commit 742b43d025e408f0650acd17317d424d980b06da Author: Olaf Seibert Date: Mon Apr 27 22:54:12 2015 +0200 Initial import of klh10-2.0a.tgz dated Nov 19 2001. Source: http://klh10.trailing-edge.com/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7b7d5f6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,80 @@ +/* LICENSE - KLH10 License Terms +*/ +/* $Id: klh10.lic,v 2.2 2001/11/10 10:41:40 klh Exp $ +*/ +/* Copyright © 2001 Kenneth L. Harrenstien +** +** The text of this license was derived from the University of +** Washington's Free-Fork License, as used on the University of +** Washington IMAP toolkit (Copyright 2001 University of Washington). +*/ + +KLH10 Free-Fork License + +The "KLH10" PDP-10 emulator, Version 2.0 +Copyright © 2001 Kenneth L. Harrenstien + +This KLH10 Distribution (code and documentation) is made available to +the open source community as a public service by Kenneth +L. Harrenstien. Contact Kenneth L. Harrenstien at klh@alum.mit.edu +for information on other licensing arrangements (e.g. for use in +proprietary applications). + +Under this license, this Distribution may be modified and the original +version and modified versions may be copied, distributed, publicly +displayed and performed provided that the following conditions are +met: + +(1) modified versions are distributed with source code and +documentation and with permission for others to use any code and +documentation (whether in original or modified versions) as granted +under this license; + +(2) if modified, the source code, documentation, and user run-time +elements should be clearly labeled by placing an identifier of origin +(such as a name, initial, or other tag) after the version number; + +(3) users, modifiers, distributors, and others coming into possession +or using the Distribution in original or modified form accept the +entire risk as to the possession, use, and performance of the +Distribution; + +(4) this copyright management information (software identifier and +version number, copyright notice and license) shall be retained in all +versions of the Distribution; + +(5) Kenneth L. Harrenstien may make modifications to the Distribution +that are substantially similar to modified versions of the +Distribution, and may make, use, sell, copy, distribute, publicly +display, and perform such modifications, including making such +modifications available under this or other licenses, without +obligation or restriction; + +(6) modifications incorporating code, libraries, and/or documentation +subject to any other open source license may be made, and the +resulting work may be distributed under the terms of such open source +license if required by that open source license, but doing so will not +affect this Distribution, other modifications made under this license +or modifications made under other Kenneth L. Harrenstien licensing +arrangements; + +(7) no permission is granted to distribute, publicly display, or +publicly perform modifications to the Distribution made using +proprietary materials that cannot be released in source format under +conditions of this license; + +(8) the name of Kenneth L. Harrenstien may not be used in advertising +or publicity pertaining to Distribution of the software without +specific, prior written permission. + +This software is made available "AS IS", and + +KENNETH L. HARRENSTIEN DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, +WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, +AND IN NO EVENT SHALL KENNETH L. HARRENSTIEN BE LIABLE FOR ANY +SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER +RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +CONTRACT, TORT (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + diff --git a/README b/README new file mode 100644 index 0000000..eeecf34 --- /dev/null +++ b/README @@ -0,0 +1,112 @@ +/* README - README file for KLH10 Distribution +*/ +/* $Id: klh10.rdm,v 2.2 2001/11/10 10:41:40 klh Exp $ +*/ +/* Copyright © 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +*/ + +Please read the LICENSE file for the full license terms. Although +there are no specific restrictions on use of this software, some rules +do apply for modification and re-distribution. Note that these terms +do not apply to files in the "run" and "contrib" directories, which +may have each have different terms and are distributed separately to +avoid licensing complications (see the READaux file if you have +extracted the Auxiliary Distribution). + +While not part of the license terms, I would like to ask that any +commercial or institutional users who derive benefit from this +software consider arranging for a modest support agreement. Without +the income from a handful of commercial licensees, it would have been +completely impossible to justify the time that went into developing +and testing this product. + +Of course, this software comes "as is" with no guarantees whatsoever, +and with ABSOLUTELY NO WARRANTY. This is particularly true for this +Distribution, which is NOT the same as the V1.x commercial version; I +took the opportunity to make several substantial cleanups and +additions which have yet to be thoroughly verified and tested. There +are still some known issues with certain platforms, but I've decided +to proceed with this release now rather than postpone it indefinitely +in the perpetual pursuit of perfection. + + --------------------------------- + +Now, if you're the impatient type who wants to get on with it, go +straight to: + + doc/install.txt + +But if you're the rarity who likes to read everything first, I +recommend starting with: + + doc/Intro.txt + +Please send me any bug fixes (preferably conditionalized for clarity), +as well as any ideas for improving the distribution itself. +Undoubtedly there will be many. I can't promise I will be able to +incorporate all of them in a timely fashion since my clients and +family will always have first priority, but will do my best. + +Thanks! + --KLH + +Net: +Snail: 759 Torreya Court, Palo Alto, CA 94303-4160 + + --------------------------------- + +CREDITS, THANKS, ACKNOWLEDGEMENTS + + I'm not sure where to put this but figure the README is + prominent enough. + +While I am responsible for all of the KLH10 software and documentation +as of this distribution, there were many people along the way who, +whether they knew it or not, helped make it happen. Everyone who uses +this software owes them a debt of gratitude! + +First, the list from the original KS10 version: + + Stu Grossman, for convincing me that a C version was feasible. + Alan Bawden & Dave Moon, who (apart from their already legendary + roles in ITS) helped as much as they could without actually + being there. + Jay Verkler, who provided a MIT-AI lab environment within Oracle. + And everyone else on the ITS-LOVERS and TOPS-20 lists whose messages + and actions have helped keep PDP-10s alive. +Plus: + Mark Crispin, without whom it would never have run TOPS-20. + Rob Austein, for lots of network and porting help; the "tun" IMP code + is based on his work. + +Now, for the KL10 version: + + Andy Riebs and Maurice Marks, who championed the KL project at DEC. + Larry Sendlosky, who more than anyone else is responsible for + the success of the KL10 version. While at DEC he dug up + whatever was needed, built and tested innumerable configurations, + and went into the field to pitch and support the emulator. + Doug and Christina Engelbart, and the rest of the Bootstrap group, + for preserving the SRI-NIC DEC-2065 just long enough. Without + a working KL and filesystem it would have been much harder. + Duane Winkler, for taking a chance. + Bill Parsons, David Alspaugh, Dennis Horn, Holly Sue White, + Jim Saleh, Mary Coppernoll, Raylene Pak, Tom Jenkins, Tom Saleh, + and many unknown others for using it and shaking the bugs out. + +And finally: + + Doug Humphrey, who supported the merge and Public ITS work. + Bob Supnik, who made it possible to release the sources. + Lars Brinkhoff, for inspiration and release testing. + Tim Shoppa, for his preservation efforts and release hosting. + +If I've inadvertently left anyone off, please let me know. + +Thanks again! diff --git a/bld/fbx86/Makefile b/bld/fbx86/Makefile new file mode 120000 index 0000000..83c8b70 --- /dev/null +++ b/bld/fbx86/Makefile @@ -0,0 +1 @@ +../../src/Mk-fbx86.mk \ No newline at end of file diff --git a/bld/lnx86/Makefile b/bld/lnx86/Makefile new file mode 120000 index 0000000..591337f --- /dev/null +++ b/bld/lnx86/Makefile @@ -0,0 +1 @@ +../../src/Mk-lnx86.mk \ No newline at end of file diff --git a/bld/lnxarm/Makefile b/bld/lnxarm/Makefile new file mode 120000 index 0000000..8fa1571 --- /dev/null +++ b/bld/lnxarm/Makefile @@ -0,0 +1 @@ +../../src/Mk-lnxarm.mk \ No newline at end of file diff --git a/bld/lnxppc/Makefile b/bld/lnxppc/Makefile new file mode 120000 index 0000000..aaf55d9 --- /dev/null +++ b/bld/lnxppc/Makefile @@ -0,0 +1 @@ +../../src/Mk-lnxppc.mk \ No newline at end of file diff --git a/bld/osfaxp/Makefile b/bld/osfaxp/Makefile new file mode 120000 index 0000000..4c851e1 --- /dev/null +++ b/bld/osfaxp/Makefile @@ -0,0 +1 @@ +../../src/Mk-osfaxp.mk \ No newline at end of file diff --git a/bld/solsparc/Makefile b/bld/solsparc/Makefile new file mode 120000 index 0000000..8e2e8f3 --- /dev/null +++ b/bld/solsparc/Makefile @@ -0,0 +1 @@ +../../src/Mk-solsparc.mk \ No newline at end of file diff --git a/doc/Intro.txt b/doc/Intro.txt new file mode 100644 index 0000000..601b057 --- /dev/null +++ b/doc/Intro.txt @@ -0,0 +1,167 @@ +/* INTRO.TXT - Introduction to the KLH10 +*/ +/* $Id: Intro.txt,v 2.4 2001/11/19 12:11:30 klh Exp $ +*/ +/* Copyright © 1997,2001 Kenneth L. Harrenstien +** All Rights Reserved. +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +*/ + + +CONTENTS +======== + + All KLH10 documentation is online in ASCII text files for +simplicity and portability. The contents, in suggested order of +perusal, are as follows: + + Intro.txt - General intro + news.txt - Changes from previous versions + install.txt - Build, install, & configuration instructions + usage.txt - General usage instructions + + klt20.txt - KL10 TOPS-20 installation and startup + klt10.txt - KL10 TOPS-10 installation and startup + kst20.txt - KS10 TOPS-20 installation and startup + kst10.txt - KS10 TOPS-10 installation and startup + ksits.txt - KS10 ITS installation and startup + + backgrnd.txt - Background comments + coding.txt - Coding guidelines + cmdref.txt - Command reference + cmdsum.txt - Command summary cheat sheet + dfkfb.txt - KL10 timing diagnostic + dvhost.txt - Special host device + history.txt - Historical notes + kldiff.txt - Differences from real KL10 + utils.txt - KLH10 Utilities + vtape.txt - Virtual tape details + + +GENERAL INFORMATION +=================== + + "KLH10" is an emulator for the PDP-10 series of machines from +Digital Equipment Corporation. The most important thing to know is +that this is a MACHINE emulator. It emulates a specific PDP-10 CPU +and all necessary I/O devices, running a site's existing operating +system (monitor) binary without change -- which then runs user-mode +application programs just as if they were on a real PDP-10. Even +floating-point results are bit-identical. + + This means sites must continue to manage the virtual system in +the same way as a real one, without the old hardware. However, the +emulator doesn't monopolize the actual platform, and runs as a set of +simple user processes that co-exist with other software on the native +system. It is possible to configure it so that no actual CPU time is +used while the virtual system is idle. + + The KLH10 emulator is extremely portable and flexible -- it is +written in ANSI C so that it can be ported to new platforms quickly, +and new "virtual hardware" can readily be added. The following +describes the currently supported versions: + + Emulation Target: KL + CPU: KL10B with extended addressing + Memory: 4MW MF20 (22 bits - 8192 pages of 512 words) + Microcode: v.442 (supports final versions of TOPS-10 or TOPS-20) + Devices available: + DTE - one CTY + RH20 - up to 8 (7 if using a NI20) + Disk - 8 RP06 or RP07 drives per RH20 + Tape - 8 TM02/3 formatters per RH20, with one TU45/TU77 each + Network - one NI20 (KLNI/NIA20) ethernet interface + + Emulation Target: KS + CPU: KS10 + Memory: 512KW (19 bits - 1024 pages of 512 words) + Microcode: ITS v.262 (supports final version of KS10 ITS) + DEC v.130 (supports final versions of KS TOPS-10/20) + Devices available: + FE - one CTY + RH11 - up to 2 + Disk - 8 RP06 or RP07 drives per RH11 + Tape - 8 TM02/3 formatters per RH11, with one TU45/TU77 each + Network - one "ACC LH-DH" IMP interface (ITS only) + + + Supported Host Platforms: + Compaq/DEC Alpha with Tru64 (formerly Digital Unix, formerly OSF/1) + SUN Sparc with Solaris + Any x86 with FreeBSD, NetBSD, or Linux + Plus: Any system providing equivalents to basic Unix system calls. + WNT/W2K ports are possible. + + Requirements: + Memory: KL: 35MB (3MB program, 32MB emulated PDP-10 memory) + KS: 6MB (2MB program, 4MB emulated PDP-10 memory) + Disk: .2-.3 GB per RP06, .5-.9 GB per RP07, 50+GB per dynamic RPxx + Tape: optional, can use any variable-record-length drive + Network: optional, LAN interface advised (can share single interface + with native OS, but a second may be desirable) + + Performance: + Varies with job mix and platform architecture, but general + observations suggest the following ratios relative to a + real PDP-10 (where 1.0 = KL10 speed): + SPARC: (MHz / 200) + Alpha: (MHz / 250) + x86: (MHz / 200) + Thus, a 450MHz x86 would provide slightly over 2x KL speed. + + +IMPLEMENTATION +============== + + The KLH10 internals are organized in a fashion roughly similar +to that of actual hardware. There is a "KN10" process equivalent to a +KS10 or KL10 processor that carries out all CPU operations and is +continuously running, except when the KLH10 user-interface command +parser has control. Most importantly, each hardware device is +implemented as a separate subprocess, with optional direct access to +main PDP-10 memory for data transfers. + +This architecture has several advantages: + + - It can take advantage of multi-processor host platforms. + - The emulated CPU never needs to wait for I/O. This considerably speeds + up operation, compared to a non-threaded emulator which must + block on disk I/O. + - It can directly use very slow physical devices such as magtape drives + (half-inch, 8mm, 4mm, DLT, etc). Virtual tapes (i.e. tape images + on disk) are also supported. + - The sub-process implementation is more portable and robust than + a threaded implementation. + - Device failures need not be fatal. The NIA20 network device for example + can be reloaded and restarted without stopping the KN10 or the + TOPS monitor. + + +TERMINOLOGY +=========== + + There are two terms used in the documentation and source that +may cause confusion: KLH10 and KN10. + +"KLH10" refers to the entire software product covered by the license. + This is theoretically a proper name ("KLH10 has") but is + often used as an object name ("the KLH10 has"). + +"KN10" refers to the virtual PDP-10 created at runtime by the + "kn10" executable image. It is specifically intended to + serve as an object name alongside the physical PDP-10s + KA10, KI10, KL10, and KS10. + +Nearly all software products are referred to like people (ITS, Emacs, +Oracle, Solaris, etc) and the same can be done with "KLH10". However, +to emphasize that it emulates a hardware device, it is often still +referred to as an object -- "the KLH10". In fact this objectification +urge is so strong that it is actually difficult to avoid using the +articles "the" and "a". The term "KN10" was invented to help maintain +the distinction between the product and the virtual machine/object. + +I will let others speculate on why English is this way. diff --git a/doc/backgrnd.txt b/doc/backgrnd.txt new file mode 100644 index 0000000..7380653 --- /dev/null +++ b/doc/backgrnd.txt @@ -0,0 +1,402 @@ +/* BACKGRND.TXT - KLH10 Background Commentary +*/ +/* $Id: backgrnd.txt,v 2.3 2001/11/10 21:24:21 klh Exp $ +*/ +/* Copyright © 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +*/ + +FAIR WARNING: + You don't need to read this file. It has nothing of + interest to most users. Hackers, however, may like it. + + It is mostly a rambling commentary on the KLH10 code that attempts + to explain why things are the way they are, and speculate on how + they might be. + And like the code itself, this is an incomplete work in progress. + + + As you read the source you will notice that some parts are +rather simplistic and specific, while others are too complex and +generalized. Many things are not fully implemented, and many others +are over-implemented. Sometimes there are good reasons for this and +other times that's just how it ended up when I ran out of time during +one development phase or another. + +A little history may help set the context: + +HISTORY +======= + + From an outside viewpoint there have been essentially two +major KLH10 versions: the first being a "toy" KS10 emulator (V0.x), +and the second a commercial KL10B emulator (V1.x). Internally, of +course, many more versions have existed as part of a long incremental +development process. + +[XXX: Flesh out later] + + Roots - PDP-10 arithmetic package for KCC cross-compiling. + First version - KS10 running ITS, then TOPS-20. + Synchronous device model. + DEC intervention - KL10B version on Alpha, commercial use. + Extended addressing, KL devices. + Device subprocess model. + Merged version - KS and KL, shared devices. Intended for + Public ITS systems. + Source release - cleanups, multiple tape format support, etc. + + +PRIORITIES +========== + + Once the decision was made to pursue a commercial KL10B +emulator, KLH10 design and implementation was driven by four main +priorities, in the following order of importance: + + [1] Accuracy + [2] I/O Performance + [3] Stability & reliability + [4] Portability + +[1] Accuracy +------------ + + The most important goal was implementing an accurate +emulation, with two facets. Not only did the emulator have to run +existing TOPS-10 and TOPS-20 monitor binaries as-is, it also had to +emulate a KL10B accurately enough for users to run their applications +and get exactly the same answers as before. Otherwise, it would be +worthless as a product. + +This is a matter of judgement since perfect emulation is impossible, +but there is a huge difference between the initial "toy" KLH10 that +first ran ITS and the current one. By far the vast bulk of the work +went into the task of making the emulator behave correctly, certainly +much more than into making it "fast". + +There were many problems. Some were difficult simply because of their +complexity, but the hardest were the ones where the documentation was +simply wrong or ambiguous in describing how a real KL10B behaved. +Even access to the microcode was not much of a help, since much of the +KL logic was actually implemented in hardware. + +Some of the issues encountered: + + - Extended addressing idiosyncracies, esp with PXCT + - EXTEND instructions - many ambiguities + - Floating point - hardware garbage + - RH20 and DC10 emulation (more "Adventures in RH20-Land") + - DTE 10-11 device emulation + - NI20 emulation + - Monitor race conditions + +The DTE and NI20 are particularly complex since in reality those +devices were self-contained processors in their own right. + +Of course, for practical reasons not everything could be emulated; see +the file "kldiff.txt" for details on the differences between the KN10 +and a real KL10B. + + +[2] Stability & reliability +--------------------------- + + For this kind of product to be worth something, it has to work +and keep working. The more problems I ran into while developing it, +the more paranoid I became about making possibly destabilizing +changes. In particular, once it was deployed to clients I tried to +make only those changes that were absolutely necessary for one reason +or another. + +I did build a large battery of tests to help verify whether or not the +internal KN10 processor was accurately emulating a real KL10B, as well +as a variety of device operations. These regression tests are still +very helpful, but they do not catch everything; they are good only up +to a point. + +As a result, once code was proven to work, it tended to remain +relatively static from there on. Only with the KLH10 source release +and its availability to many more potential testers did I feel safe in +finally starting many long delayed cleanups. + +What this means is that the V2.x sources you have are not the same as +the commercial V1.x versions. They are intended to be better, but +have not undergone anything close to the same amount of testing and +verification. At some point, of course, V2.x should be stable enough +that it can replace V1.x with minimal perturbations. + + +[3] I/O Performance +------------------- + + The biggest problem with the initial toy KLH10 was its lack of +asynchronous I/O; this meant that any device I/O would always be +"instantaneous" in the sense that all CPU operations would block +completely until the I/O was finished. The CPU blockage is okay for a +single-user personal system, but unacceptable for a true time-sharing +system and completely prohibitive if trying to support real magnetic +tape drives. + +The solution adopted was to implement devices as separate Unix child +processes (the DP filename prefix stands for "Device Process"), allowing +them all to run independently and asynchronously. + +[XXX: flesh out a bit more] + + + +[4] Portability +--------------- + + While always desirable on general principles, portability is a +somewhat idealistic goal -- there is no absolute requirement for it, +unlike the other issues. The main reason the emulator remains +portable is because I wanted it that way in preparation for the day +when it could be distributed. + +There is nothing in the KLH10 code that requires an integer +type larger than 32 bits, nor does it require any features not found +in ANSI C. In fact, for a long time it did not even rely on ANSI +features either. + +At the time it was first written, this was an inescapable requirement +because 64-bit types, not to mention ANSI compilers, were few and far +between; even GCC's "long long" had early bugs. With the Alpha +version 64-bit code became possible, but nothing has ever relied on +it. + +This is why every reference to a PDP-10 word or value is done by means +of a macro. Even on platforms that support 64-bit types, the +architecture sometimes works better when restricted to 32 bits. + +From the viewpoint of OS (rather than CPU) portability, the code again +tries to rely on ANSI C library functions rather than UNIX system +calls, but it is fair to say that most of the existing OS-dependent +support is Unix oriented, especially for the real-time asynchronous +version. Fortunately with the advent of true operating systems for +both MacOS (X) and Windows (NT, 2K, XP), this should less of a problem +in the future. + + +Non-issue: CPU speed +-------------------- + + It may seem odd to people who have not run a business or +service, but the speed of the emulated PDP-10 has never been a real +issue. In practice clients have just picked a hardware platform that +provides acceptable performance, and as soon as their systems are up +and working, they prefer to see as little change as possible. There +is little compulsion to upgrade with faster or more featureful versions +unless something is actually broken, which should only happen if the +platform OS is upgraded to yet another incompatible release. + + + +MAJOR DESIGN ISSUES +=================== + +C vs Assembler +-------------- + + The notion of a PDP-10 emulator had been bouncing around for +some time before I decided to embark on the project, and performance +was always the number one issue; it was not clear whether it could be +made to execute PDP-10 code fast enough to be useful. A typical +workstation was a 33MHz SPARC-2, and the x86 PCs were still 386s. +Several people felt the best way to achieve this was to pick a host +architecture (SPARC or the forthcoming Alpha) and code in assembly +language. + +As a long time PDP-10 assembler fan, this approach appealed to me as +well, but eventually I came to the conclusion that this would be best +attempted in C, for two major reasons: + + - Portability. After having engineered a major porting effort + from TOPS-20 to Unix while at SRI, I never wanted to be locked + into a specific machine architecture again. Oracle's + extremely impressive porting system provided additional + conviction that this was the right approach. + + - Implementation time. It was much faster to write in C than + assembler, especially for RISC-based machines. + +There was also a third, subjective reason -- at the time, Stu Grossman +was writing a simple prototype in C called "KX10". While I was unable +to use anything from it, it did convince me that C was the right way +to go, and as far as I know Stu was the first to attempt it. + +As it turned out, this decision also proved to be the right one in +terms of performance. I had anticipated that keeping the code +portable would eventually lead to "free" performance gains as hardware +improved over the years, and that was in fact the case. But one other +factor surprised me: C code was sometimes faster than assembly code! + +It took a while to realize that for the new RISC-based machines with +pipelines and caches, new rules applied; often the C compiler provided +with a machine would know about tricks or scheduling rules that not +only were difficult for a human to code by hand, they also could +change from one machine to the next. While doing timing tests on +initial versions of the emulator, I found performance so sensitive to +tiny and logically unrelated changes that I finally gave up worrying +about it. + +In retrospect all this may seem obvious, but it was not so at the +time. + + +Instruction Execution Loop +-------------------------- + + The decision to use C posed some problems with respect to +control flow. The microcode of real machines is like a huge plate of +extremely sticky spaghetti where every "instruction" has a jump +address that can go anywhere else, plus a bunch of chopsticks stirring +the mess up with external interrupts and asynchronous device +operations. + +By contrast, C favors the use of separately defined functions with a +stack-based calling convention, and control flow strongly favors using +the normal call-return mechanism. It is possible to bypass the latter +with "longjmp" type invocations, but you can only jump to pre-existing +contexts and the mechanism can be very expensive. For someone used +to the freedom of assembly language, this is very frustrating. + +Without going into all possible ways of implementing a PDP-10 emulator +in C, there appeared to be two main choices: + + - Stuffing as much as possible inside a single huge function. + Normally this would involve using a large switch() statement + for instruction dispatch, plus assorted gotos to accomodate + weird control flow issues. + + - Looping within a small function and dispatching to each + instruction as a function call. + +(Note that the PDP-10 has one characteristic that makes things a +little easier -- every instruction must always calculate an effective +address regardless of whether it is used or not, so this naturally +leads to building an instruction execution loop with the EA +calculation as its central focus.) + +The KN10 code uses the latter approach for a number of reasons, some +of which no longer apply. When it was first written, compilers still +existed that had problems with very large switch statements, and even +now it is still considered easier for them to optimize a function if +it does not contain "goto" statements. Keeping instructions in +separate routines also made it easier to invoke them explicitly as +needed (for XCT, PXCT, etc), examine their assembler output to see +what was being produced, and step through them with a debugger. + +This is almost certainly not an optimal design for speed. + + +PDP-10 Word Representation +-------------------------- + +[XXX - some stuff about the tradeoffs, from word10.h] + + +FUTURE DIRECTIONS +================= + + Since the odds of new commercial applications showing up are +essentially nil, whatever happens next will be entirely up to the +PDP-10 hacker community. It will either evolve with their help or +quietly go away without it. + +Recently a number of other PDP-10 emulators have finally appeared, +most or all of which have the KS10 as a target. I have not yet looked +at them (partly to avoid contamination) and so have no basis for +comparison, but in general I think multiple KS implementations are a +good thing, and as software tools improve it should become easier. +The KS is a fairly clean yet respectable machine that would make an +excellent semester project, and was fun to do. The more people who +can get their hands dirty, the better. + +The KL10 by contrast was much harder, and definitely not fun. I hope +that with the KLH10 release no one else will have to go through that +nightmare, or at least will be able to start from a far better place. + + +FUTURE KLH10 IMPROVEMENTS +========================= + +The wish-list stack has always been a mile high. Here's a bunch of +stuff off the top of my head, in no particular order. + +- Multi-processing extensions: + - Decouple FE from KN10. + - Allow other user processes to examine KN10 state and manipulate + devices (especially virtual magtapes) without requiring + console access. + - Re-investigate threading (very carefully; very unportable). + +- The UI sucks: + - Improve present UI, add editing. + - Allow input scripts to feed OS console at startup. + - Add GUI interfaces: + - 340 display (Spacewar lives!) + - FE/Console (KA10 panel with blinkenlights!) + - Magtape UI + - CPU and device visualizations + +- Accuracy improvements: + - Get rid of the last low-bit FDV divergence, even + though that may reduce mathematical accuracy. + - Implement address break (optional - very slow). + +- Raw performance: + - Develop better performance metrics (TenStones? DECmarks?). + - Speed up floating point (DFDV is far and away the + slowest instruction). + - Alternative word representations. + - EA calculation and memory mapping (most of the CPU is burned + on this, I believe). + - Characterize workload to determine true bottlenecks. + - Alternative instruction loops: + - Hybrid switch & dispatch + - Hand-coded assembler (x86 primarily) + - Locality improvements; must match to host cache setup. + - Autoconf mechanism to run tests on host and automatically + select optimal build configuration. + +- Network support: + - Add AN10 device, couple with IP tunnels like "tun". + - Determine OS mods to allow self-connection for systems that + currently can't do it (Linux, FreeBSD, Solaris). + Get mods incorporated in standard releases. + +- Magtape support: + - Finalize tape naming conventions. + - Non-console access (cf UI above). + - Finish in-memory support. + - Add update capability. + +- Emulator extensions: + - Finish KA10, KI10 versions. TENEX pager. WAITS? + - Emulate SC-40 and/or XKL (more "physical" memory). + - Additional devices (many!) + - DH11 (KS10), or extend DTE (KL10) + - AN10 (IMP interface) + - DX20, CI, ... + - Emulate NCP with IMP interface for older OSes. + +- PDP-10 software: + - Public ITS support. + - Further KN10-conditionalized OS mods. + - Resurrect older KA/KI OSes. + - GCC on KL (Lars). + +- Distribution: + - Add HTML versions of doc. + - Better build mechanism. + - Permission for Public ITS distribution (legal hoops). + - Packaged ITS filesystems. + - Packaged DEC OS filesystems. + - Packaged NIC/Stanford filesystem. diff --git a/doc/cmdref.txt b/doc/cmdref.txt new file mode 100644 index 0000000..99c946c --- /dev/null +++ b/doc/cmdref.txt @@ -0,0 +1,547 @@ +/* CMDREF.TXT - KLH10 Command Reference documentation +*/ +/* $Id: cmdref.txt,v 2.3 2001/11/10 21:24:21 klh Exp $ +*/ +/* Copyright © 1994-1999, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +*/ + +KLH10 COMMAND SYNTAX: + + The KLH10 command parser is extremely simple-minded, since +normally very little user interaction is needed. In particular, it is +not intended to be a general-purpose debugger, although it does have +various commands to help debug the "hardware". + + Each command is one complete line, and command verbs need only +a unique initial substring. Case is not significant anywhere in KLH10 +commands, except for filenames that must be given to the native OS. + + The various parts of a command must be separated by spaces or +tabs. The backslash (\) character can be used to quote the next +character, but if you think you need this, you are probably trying +something funny that may not work. Lines cannot be continued with +backslash; just keep typing. + + +KLH10 COMMAND REFERENCE: (in ASCII lexical order) + +^\, CONTROL-\, ASCII "FS" + This is not exactly a KLH10 command; it is the command escape + character, which can be typed anytime the KN10 is running. + It should halt the KN10 immediately with the message + + [HALTED: FE interrupt] + + Followed by the KLH10 command parser prompt: + + KLH10> + + Typing ^\ while in the command parser should have no effect, + although this depends on the native OS. + + +1-STEP + Single-steps the KN10 by executing one instruction. For debugging. + This is the same as "proceed 1". + + +BREAKPT + Set PC breakpoint to . For debugging. + + Only one PC breakpoint can be set. It does not modify memory + in any way. Use an of 0 to turn it off. + Execution is slower when the PC breakpoint is active, so don't + leave it on if you don't need it. + + Currently, only the in-section part of the address is significant + (low 18 bits). The KN10 will be stopped whenever the low 18 bits + of PC match the specified value, whether the CPU is in user or + exec mode. + + +CONTINUE + Continues the KN10 at the current PC. + This is the normal method of continuing after interrupting the + KN10 with ^\. + + +DEPOSIT + Deposits the word value at the given address. For debugging. + See the description of the LOAD command for the format of . + + The should be one or two octal constants of the form + or ,,. Instruction mnemonics are not recognized, sorry. + + +DEV + Execute a device-dependent command. + must be already defined by the DEVDEFINE command. + This is provided as a way for device drivers, especially dynamically + loaded ones, to accept arbitrary commands if they wish to do so. + No current driver uses this. + + +DEVDEBUG [ []] + Set or show device debug values. + DEVDEBUG - Shows values for all known devices. + DEVDEBUG - Shows value for that device. + DEVDEBUG 0 - Turns off debug tracing for device. + DEVDEBUG 1 - Turns on basic debug tracing for device. + DEVDEBUG 3 - May turn on verbose tracing for device. + + must be already defined by the DEVDEFINE command. + Each device may have a different interpretation of a non-zero + debug value. In general, when debug tracing is turned on, activity + on that device will cause messages to be printed on the standard + error stream, which is normally the KLH10 CTY. + + Note that devices may output error messages at any time whether or + not debug tracing is in effect. + + +DEVDEFINE + Define, bind, and initialize a KN10 device or controller unit. + See the file "install.txt" for more details. + + - A short (6-character) name of your choice to + uniquely identify this device. Examples might be + dsk0, dsk1, mta0, etc. These names are meaningful + only to you; the KLH10 doesn't care, nor are they + passed to the KN10 in any way. + + The is how you can refer to the device + when using other KLH10 commands such as DEVMOUNT. + + - The KN10 device number you are binding. + For all machines but the KS10, this is normally + an octal device number such as would be used in + I/O instructions (DATAI/DATAO, etc). For example, + use 200 for DTE#0, 540 for RH20 #0, and so on. + + The is how the KN10 can refer to the device. + + - Specifies what the device actually is, by + identifying the "emulator driver" you are binding + this device to. The driver is a software module that + is invoked to support all references to the device, + whether from you or from the KN10. + + Giving the "devshow" command will list all of the known + drivers (currently DTE, RH20, RP, TM03, NI20). External + (dynamically linked) drivers are supported but untested; + see the "devload" command. + + - Various parameters specific to the + device driver type, usually optional. Their syntax is + normally of the form =, but sometimes + just the is sufficient. + The ordering of these parameters does not matter; nor + does the case of the keywords. + + NOTE!!! There is currently no way to undefine a device. + Once defined, a lasts until the user quits the KLH10. + + +DEVEVSHOW [] + Show device event registration info. Primarily for debugging. + must be already defined by the DEVDEFINE command. + +DEVLOAD + Load a DLL device driver. + This command allows device emulator drivers to be loaded dynamically + at runtime rather than being linked into the KLH10 binary at + compile time. + Warning: This almost certainly WILL NOT WORK yet for your platform. + It is intended to provide a way for people to write their own + device emulator drivers independently of those provided + or built into the KN10. + + +DEVMOUNT [] + Mount device media. + must be already defined by the DEVDEFINE command. + are device dependent. + + Note that this command can only make a polite request to the device, + which may respond with a failure message. If the device is busy, + the request may be ignored immediately with an error message; try again + later. Any currently mounted media will be automatically unmounted, + but it's best to explicitly DEVUNMOUNT a device before trying + to DEVMOUNT something on it. + + DEVMOUNT is used primarily for mounting virtual tapes on the + TM02/TM03 driver, although it is intended to work for disks as well. + + The options for the TM02/TM03 depend on whether you are mounting + a virtual tape or a physical tape drive. + + For a physical drive, you must specify: + HARD - is a physical tape drive. No other + options will be recognized, not even RO. + + If HARD is not specified, a virtual tape is assumed. + For virtual tapes, there are several options: + + FMT= - Tape format (RAW, TPS, TPC, etc; see vtape.txt) + MODE= - One of READ, CREATE, or UPDATE. + FSKIP=<#> - Skip <#> files/tapemarks when mounted. + UNZIP[=] - TRUE to allow uncompression if suggests it. + plus for convenience: + RO, READ - same as MODE=READ (read-only) + RW, CREATE - same as MODE=CREATE + UPDATE - same as MODE=UPDATE + and for debugging: + DEBUG[=] - TRUE for debug output from vmtape. + PATH= - Overrides command + CPATH= - Overrides control path derivation + RPATH= - Overrides data (raw) path derivation + + The normal defaults are now FMT=TPS and MODE=READ. + (Formerly they were RAW and RW) + + Example: + @assign mta0: ; At T20 EXEC + @i dev + Devices assigned to/opened by this job: MTA0, TTY105 + @[HALTED: FE interrupt] ; ^\ typed here + KLH10> devmo mta0 mybackup create ; Mount virtual R/W tape + Mount requested: "mybackup" ; Request sent to device + KLH10> [mta0: Tape online] ; Asynch response looks OK + ; (Typed CR for fresh prompt) + KLH10> c ; Continue KN10 + Continuing KN10 at loc 01117513... + + @dumper ; Back at T20 EXEC + DUMPER>tape mta0: + DUMPER>save ps:*.*.* (AS) + [...etc...] + + Note in the above example that the "mta0" only happens to + look like the TOPS-20 device designator "mta0:" because a KLH10 + DEVDEFINE command at startup defined it thusly for convenience. + Don't confuse them. + + +DEVSHOW [] + Show information about device definitions. + must be already defined by the DEVDEFINE command. + If no device is specified, all are shown; this can be useful. + + If a is specified, it is queried for whatever information + it wants to show. Currently none show anything, so this form isn't + very interesting. + + +DEVUNMOUNT + Unmount device media. + must be already defined by the DEVDEFINE command. + This is particularly useful for magtape devices, but is intended + to also work for disks someday. + + IMPORTANT! This command, like DEVMOUNT, can only make a polite + request to the device. + If the device is busy, the request is ignored immediately + with an error message; try again later. + + Example: + DUMPER>quit ; Leaving T20 DUMPER pgm + @[HALTED: FE interrupt] ; ^\ typed at T20 EXEC + KLH10> devunmount mta0 ; KLH10 cmd given + Unmount requested ; Request looks OK + KLH10> c ; Continue KN10 + Continuing KN10 at loc 01117511... + + @ ; Back at T20 EXEC + + +DEVWAIT [] [] + Waits until device sub-process(es) are ready to accept new + requests. + + If is specified, only that device is waited for. + Otherwise, all devices are waited for. + + If is specified, the command will only wait for that + many seconds. Otherwise, it waits indefinitely. + + This is primarily intended for use in startup scripts, before + (or after) a mount or unmount request is made to a disk or + magtape device. It ensures that further commands are not + executed until the devices are ready for them. + The likelihood that this will ever be needed is small, but + the command exists anyway. + + +DUMP + Dump binary from KN10 memory into a native OS file. This is the + inverse of LOAD. + Only the first 256K of memory is dumped; the data-format used is + the same as the current LD_FMT setting, and the load-format is SAV. + Zero words are not written out. + + +EXAMINE [] + Show word at address. For debugging. + Sets the current location ("dot") to the specified address; if no + address is given, the current location is used. + The PDP-10 word value is printed out. If it appears to be a + valid PDP-10 instruction, it is shown in instruction format as well. + + The may be "local" or "global"; if in the form ,, + it is assumed to be global. This can be specified explicitly by + prefixing the address with either of the letters: + L - Address is "local" (relative to some section) + G - Address is "global" + + The can also be prefixed with one of the following letters + to specify the memory mapping to use: + C - Current mode's memory map (the default) + E - Exec mode memory map + U - User mode memory map + P - Physical address + A - AC address, interpreted as ,, + + Not all combinations are meaningful in all situations. + EXAMINE is always safe; you cannot cause a page fault with + an EXAMINE reference. "??" will be printed if no mapping exists. + + +GO [] + Start KN10 at address. + Ignores the current PC and sets it to the address, if one is given. + If no address is given, the start address from the most recent + "load" command is used. + + Example: + KLH10> go + Starting KN10 at loc 040000... + + BOOT V11.0(315) + + BOOT> + + +HALT + Halt KN10 immediately. + Currently this is a no-op because if you're in the KLH10 command + parser that means the KN10 is already halted! + When the KLH10 allows concurrent execution this will become a + meaningful command. + + +HELP [] + HELP - Shows brief help for all commands. + HELP - Same for all matching commands. + + The HELP command is intended more for remembering commands than + for documenting them, so don't expect much. Keep this reference + file handy. + + +LOAD + Load binary into KN10, sets start-addr if specified by file. + This is how a PDP-10 bootstrap is initially loaded. The KLH10 + uses the native OS filesystem as its "front-end" filesystem and + can load any required bootstrap directly from ordinary files. + + The binary file must, of course, be formatted correctly with + respect to both its data-format (how PDP-10 words are stored in + 8-bit bytes) and its load-format (typically EXE). + + The default data-format is core-dump, "c36" ("u36" for KSITS version). + This can be changed with the command SET LD_FMT= if necessary. + + Provided the selected data-format correctly matches that of the file, + LOAD is able to determine the load-format automatically (SBLK, SAV, + EXE) and determine the start address of the program. However, if + the data-format is wrong, LOAD will blindly load garbage; check + to see if the start address it reports looks reasonable. + + Example: + KLH10> load boot.sav + Using word format "c36"... + Loaded "boot.sav": + Format: DEC-CSAV + Data: 4632, Symwds: 0, Low: 040000, High: 054641, Startaddress: 040000 + Entvec: JRST (120 ST: 00, 124 RE: 00, 137 VR: 0,,0) + KLH10> + + +NEXT-EXAMINE + Show next word, for debugging. + Increments the current location ("dot") by 1 and then does "examine". + + +PROCEED <#> + Single-steps the KN10 by the specified number of instructions. + Most useful with TRACE-TOGGLE. + + +QUIT + Quit emulator. Asks for confirmation, since this kills the + program entirely. You should never kill or exit a KLH10 + process except with QUIT, or there will be a lot of leftover + subprocesses and memory segments littering the native system. + + For an example, see the SHUTDOWN command. You don't have to + use SHUTDOWN prior to QUIT if you don't care about stopping your + PDP-10 monitor gracefully. + + +RESET + Reset KN10. For debugging. + + +SET [[=]] + Set/show KLH10 variables, primarily for debugging. + + SET - Shows all known variables + SET - Shows value of that variable + SET = - Sets variable to specified value. No spaces! + + Showing all known variables produces a long list; most of that + information is of use only when debugging. The variables + that a user might plausibly want to set follow below. + +SET SW= - Set KN10 data switches + This is sometimes needed for diagnostics or bootstrap loaders, + which read the data switches as a crude form of input. + Example: + KLH10> set sw=20 + sw: 0,,0 => 0,,20 + KLH10> + +SET LD_FMT= - Set data-format for LOAD/DUMP + LD_FMT is a bit of a misnomer; it may be renamed. It actually + refers to the data format. + The data format specifies how 36-bit words are read from or + written into a sequence of 8-bit bytes. When 9-track tape drives + were introduced a variety of formats were created to address this + issue, and these formats have all been provided as options + for KLH10 I/O as well. + C36 - "Core-Dump" - Default. + H36 - "High-Density" - Most compact format, same as FTP Image. + A36 - "Ansi-Ascii" - Weird handling of low bit. + S36 - "SIXBIT" - used for 7-track tapes. + U36 - "Unixified" - Alan Bawden's archival format + See "Tape Formats" in VTAPE.TXT for more details. + +SET MEM_LOCK= - Set "ON" to lock emulator in memory + If set TRUE, the emulator and all of its subprocesses call + mlockall() to ensure that none of them are ever swapped out. + You must be running the emulator as root for this to do anything. + If used, this should be set prior to defining any devices. + + This results in locking down about 34M of RAM (6M for a KS10) + so the overall system should have at least 64M and preferably more. + + !!! WARNING: there is a kernel bug in Digital Unix (up to at + least version 4.0B) which can cause the entire system to eventually + hang up when mlockall() is used in this way. It's unclear whether this + has been (or will be) fixed in 4.0D or later versions. + Solaris does not suffer from this problem. + FreeBSD does not implement mlockall(). + + +SET PROC_PRI= - Set to increase emulator process priority + The PROC_PRI parameter gives its argument to the setpriority() + call; the valid values range from -20 (highest priority) to 20 (lowest + priority). You may need to experiment with this setting to find the + value that gives you the best combination of emulator and general Unix + system response; I suggest starting with -10. + You must be running the emulator as root for this to do anything. + + Note that the DPNI20 subprocess always runs at -20 to ensure + fast network response; to avoid interfering with this, you should not + set PROC_PRI to its "maximum" of -20. -19 is quite enough to give the + emulator so much priority that all other Unix system processes are + noticeably slower. + + +SET CLK_ITHZFIX= - Set to specify interval timer clock resolution + The CLK_ITHZFIX parameter fixes the KN10 clock tick rate in + Hz (ie ticks per second). This originally defaulted to 30Hz as a + compromise designed to allow the emulator to run on slower hardware + platforms without spending all its time processing clock interrupts. + However, faster platforms can now handle the full clock rate that + TOPS-10 or TOPS-20 expects, so the default is now 60Hz. You have the + option of changing this parameter to either enforce a different rate, + or (by setting it to 0) allowing the monitor to change the rate as it + pleases. + Recommendations: + - If running on slow hardware you may need to set this back + to 30 in order to get useful work done, regardless of + the PDP-10 system being used. + - For stand-alone processor diagnostics, set (or leave) this at 60. + This is particularly true for the DFKFB timing test. + - For ITS set (or leave) this at 60. This is the normal clock rate. + - For TOPS-20 try setting this to 0. The monitor's desired + rate is 1000 and system response on a fast machine may + improve since the scheduler will be invoked much more often + and its overall behavior will be closer to that of a real KL10. + If things seem slower, use a smaller value like 100 or 60. + - For TOPS-10 set (or leave) this at 60. This is the monitor's + normal clock rate, and it is NOT recommended to let it + free-run with 0. + The reason is that the TOPS-10 monitor twiddles the interval + incessantly in a futile attempt to compensate for what it + thinks are deviations when compared to the real-time base + clock. "Fixing" the rate at 60 keeps it stable. + +SHUTDOWN + Halt OS gracefully. + This only works with a moderately healthy PDP-10 monitor. It + deposits -1 into location 30 and continues the KN10. The monitor + is supposed to notice this, carry out final shutdown operations, and + then execute a HALT instruction, which returns control to the + KLH10 command loop. + + Example: + Shutdown complete ; Printed by PDP-10 OS + [HALTED: FE interrupt] ; ^\ typed to get FE int + KLH10> shutdown ; Now can give SHUTDOWN cmd + Continuing KN10 at loc 01117511... + **HALTED** + [HALTED: Program Halt, PC = 1120252] + KLH10> quit ; Now give QUIT to leave KLH10 + Are you sure you want to quit? [Confirm] + Shutting down...Bye! + % + + +TRACE-TOGGLE + Toggle execution trace printout. For debugging. + + +VIEW + View KN10 status (PC, etc). Primarily for debugging, but + harmless for the curious. + + Example: + KLH10> view + KN10 status: EXEC + PC: 1117506 [JPC: 1117513 UJPC: 30144 EJPC: 6126407] + Flags: 704040 + Next: OCcID 1117506/ SKIPE 333013 333013/ 0 + KLH10> + + A little explanation: the JPC is the PC of the last jump instruction. + UJPC and EJPC hold the JPC as of the last transition out of user + or exec mode, respectively. The flags are the left-half PC flags; + the "Next:" line shows the next instruction that will be executed + when the KN10 is continued. + + +ZERO + Zero the first 256K of memory. For debugging. + + +^-EXAMINE + Show previous word, for debugging. + Decrements the current location ("dot") by 1 and then does "examine". diff --git a/doc/cmdsum.txt b/doc/cmdsum.txt new file mode 100644 index 0000000..10ea7c0 --- /dev/null +++ b/doc/cmdsum.txt @@ -0,0 +1,58 @@ +/* CMDSUM.TXT - KLH10 Command Summary +*/ +/* $Id: cmdsum.txt,v 2.3 2001/11/10 21:24:21 klh Exp $ +*/ +/* Copyright © 1994-1999, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +*/ + +This is a cheat-sheet summary of all KLH10 commands, grouped roughly +by functional category. See "cmdref.txt" for more details on each. + +BASIC: + + help [] Shows help for all matching commands (defaults to all) + load Load binary into KN10, sets start-addr + go [] Start KN10 at address (defaults to start-addr) + continue Continue KN10 (resume execution after ^\ or halt) + shutdown Halt OS gracefully (set loc 30 to -1, then continue) + quit Quit emulator + +DEBUGGING: + + set [[=]] Set/show KLH10 variables (if no args, show all) + view View KN10 status (PC, etc) + examine [] Show word at address + next-examine Show Next word + ^-examine Show Previous word + deposit Deposit value at address + + breakpt Set PC breakpoint to + 1-step Single-step KN10 + proceed <#> Single-step # instrs + trace-toggle Toggle execution trace printout + continue Continue KN10 + + dump Dump binary from KN10 + halt Halt KN10 immediately + reset Reset KN10 + zero Zero first 256K memory + +DEVICE CONFIG/CONTROL: + + devdefine [] Define/init device + devshow [] Show defined device info + devmount [] Mount device media + devunmount Unmount device media + +DEVICE MISC: + + devdebug [] Set device debug value (0=off) + devevshow [] Show device event reg info + dev Execute device-dep command + devload Load DLL dev driver diff --git a/doc/coding.txt b/doc/coding.txt new file mode 100644 index 0000000..3036215 --- /dev/null +++ b/doc/coding.txt @@ -0,0 +1,195 @@ +/* CODING.TXT - General coding rules for KLH10 +*/ +/* $Id: coding.txt,v 2.4 2001/11/10 21:24:21 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +*/ + +KLH10 General coding rules + + +STYLE AND STANDARDS +=================== + + The style rule is simple: use the existing style. There is no +religious reason for this particular style, but it is always best for +edits and additions to remain consistent with what's already there. + +All code should conform to the first ANSI C Standard (ANS X3.159-1989, +also called the 1990 Standard). Do not use or assume features from +later versions of the C Standard, or from any specific compiler, +unless conditionalized such that everything will still build without +those features. This specifically includes pragmas; none are +currently used. Similarly, the "long long" datatype cannot be assumed +to exist. + +Do not use C++. Eventually this may become unavoidable for GUI +wrappers, but the existing core code must remain in C. + +Simply using ANSI C is not enough to ensure code is portable. You +must also be careful about any assumptions regarding the host CPU: +little-endian vs big-endian, integer type sizes, printf arguments, and +so on. This is a long and complex topic that boils down to "don't do +stupid things". + + +PORTING AND OSD CODE +==================== + + OSD means "Operating System Dependent". To make it easier to +port the KLH10 to new platforms, all system calls or non-ANSI library +facilities should be encapsulated within one or more of the OSD*.C +modules, and accessed only by facilities with an "os" name prefix. +The bulk of this code is presently in OSDSUP.H and OSDSUP.C, which is +dedicated to the KN10 component and not used by any other programs. + +The only exceptions are DPSUP.C and the separate DP* programs that use +it, but even those may be changed in the future to follow the same +conventions. + +This should be done even for system calls that are considered +"standard" for all Unix systems, such as read(), because of the +existence of certain popular non-Unix platforms. + +New platforms will almost certainly require new Makefiles. Try to +follow the scheme already in place. + + +WORD MANIPULATION +================= + + NEVER, ever, operate on a PDP-10 word (w10_t) except with the +macros provided from k10ops.h and word10.h. The only things you can +do to words without macros are pointing to them (&) or copying (by +assignment or parameter passing). + +The KLH10 is designed to be portable to environments where the longest +native integer type is 32 bits, so a w10_t may actually be a structure +or union rather than an integer. + +An important ramification of this is that any arithmetic done on +values that might exceed 32 bits must always provide special handling; +you cannot merrily assume that a sufficiently large "long long" will +always be available. Even when it is, the "native" arithmetic may not +be as efficient as a 32-bit algorithm! + + +ARG/RET CHECKS +============== + + An important rule for macros and functions: + Arguments are always *assumed* to be correctly formatted or masked. + Results are always *guaranteed* to be correctly formatted or masked. + +This is important in order to maintain consistency and avoid redundant +operations. The rule permits incorrect values to be generated or +maintained, as long as they are corrected before being given to other +facilities. + +There are only two known exceptions to this rule: mr_PC and E. + * mr_PC is not masked during the normal execution loop, to save time. + This may change for the KLX version. + * E is not masked when provided to instruction routines, also to + save time where the mask isn't needed. + + +FLOATING POINT +============== + + Do not use it. No native implementation corresponds exactly +with that of the PDP-10, and floating point results are inherently +unportable, especially for extreme operands that may even trap. + + In theory it may be possible to accelerate some PDP-10 +floating point operations with carefully selected uses of native FP +operations or libraries, but this will take a VERY large amount of +work to verify that the results remain bit-identical with those of a +real PDP-10. + + +CODING INSTRUCTION ROUTINES +=========================== + + All instruction routines must be declared in this way: + + insdef(i_xxx) + { /* Function code */ + } + +The "insdef" macro declares the function with the following three args: + int op; /* 9-bit opcode of instruction */ + int ac; /* 4-bit AC field of instruction */ + vaddr_t e; /* Effective Address calculated from I(X)Y */ + (WARNING! for KS10 may have junk in high bits!) + + The op and ac values have already been masked. + Note that someday "op" may go away. + + +All memory references should be checked BEFORE anything is modified +either in memory or the ACs! Actually, it is good practice to verify +the memory reference before even starting the computation, especially +if there's a chance that PC flags such as TRAP1 might be set. +This ensures that abnormal termination (due to page failure or PI +interrupt) doesn't leave anything messed up. +Exceptions are BLT and IDBP/ILDB which know how to back out. + +Normal memory operand reads and writes should be done with: + word = vm_read(e); - Read word, may page-fail! + vm_write(e, word); - Write word, may page-fail! + + Halfword variants vm_{read,write}{lh,rh}() also exist, but note + that they operate on h10_t integer values, not w10_t words. + +If the same memory location is to be read-modified (both read and +written) then these should be used: + vmptr_t p; + p = vm_modmap(e); - Get phys mem ptr (may page-fail!) + word = vm_pget(p); - Read word from phys mem (won't fail) + vm_pset(p, word); - Write word to phys mem (won't fail) + +BLT, Stack, and Byte operations do special-case memory referencing to +allow for previous-context mappings by PXCT. + +AC reads and writes are done with: + word = ac_get(ac); - Read AC + ac_set(ac, word); - Set AC + + Halfword AC reads and writes have ac_{set,get}{lh,rh}() + but note that they operate on h10_t integer values, not w10_t words. + +Multi-word operations (in particular the double-word instructions) must +reference each word individually, because AC+1 may wrap around (modulo 020) +and E+1 may likewise either wrap or cross a page boundary. + +The return value is added to the PC, so normally this will be 1 +except for jumps (0) and skips (2). The macro definitions PCINC_n +are used for this purpose; someday they may have different values. + +Instructions which set any of the PC flags should do so with the macro + PCFSET(flags) +to ensure that various transition subtleties are handled properly. + + +PRINTF ARGS +=========== + + This is a generic portability tidbit. All printf-style +references to integer variables of unknown size (that is, defined by +typedefs) must promote the value to long and use %l in the format +string. In particular, h10_t and vaddr_t types require this. + + +MISCELLANY +========== + + To keep some strict compiler modes happy, all functions with +external linkage must have a prototype declaration prior to their +definition. This is not needed for static functions, unless of course +they are referenced prior to their definition. diff --git a/doc/dfkfb.txt b/doc/dfkfb.txt new file mode 100644 index 0000000..7feb187 --- /dev/null +++ b/doc/dfkfb.txt @@ -0,0 +1,119 @@ +/* DFKFB.TXT - README for DFKFB KL timing diagnostic +*/ +/* $Id: dfkfb.txt,v 2.3 2001/11/10 21:24:21 klh Exp $ +*/ +/* Copyright © 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +*/ + +NOTE: + DFKDB is not KLH10 software; it is a KL10 instruction timing + diagnostic written by DEC and may only be used with a valid TOPS-10 or + TOPS-20 license. If present in the distribution, it is included + purely as a convenience for developers. + +People with a weakness for instant gratification, which is most of us, +will like DFKFB. As soon as you have built a KN10-KL, you can +immediately run DFKFB without spending any time on installation or +configuration, and get an idea of how it compares with a real KL. + +[1] Build a KN10-KL for your native platform. + $ cd /bld/ + $ make base-kl + +[2] Go to the DFKFB directory, if present, and run it. + $ cd ../../run/dfkfb + $ ../../bld//kn10-kl dfkfb.ini + +[3] Compare output with other configurations, platforms, etc. + Strut or sulk as appropriate. + +As with all benchmarks, the DFKFB output must be taken with large +grains of salt. Its results will be strongly influenced by the cache +behavior of your platform; with modern hardware it will usually run +entirely in the cache and thus will represent tne best speed you can +get. When running a PDP-10 OS with real applications, you will get +many more cache misses and the following factors become more +important: + + - PDP-10 instruction mix. Floating point is slow. + - I/O response (fast physical disks make a difference). + - Non-KLH10 processes competing for the native platform's CPU. + +Still, it's fun to see how many multiples of a real KL you can run at. + + +THE REAL THING +============== + + Unfortunately, although I have output logs of DFKFB runs for a +variety of platforms, I do not have one for a real KL10 itself! +Hopefully someone will be able to contribute this from their archives. + +The best information I can provide comes from a user-mode timing test +program I wrote called "ZOTZ"; the results I have are for a DEC-2065, +which is a KL10B with MCA25 cache. Even though this is a user-mode +program rather than exec-mode as for DFKFB, these numbers should be +within a few percent of the real thing. Copying this data into the +format of DFKFB gives the following simulated output, with "??" +inserted where nothing was available: + +1 - BASIC CLOCK CYCLE IS ?? NSEC. +2 - INDEXING TAKES 36 NSEC. +3 - INDIRECT TAKES 269 NSEC. +4 - INDEXING AND INDIRECT TAKES 336 NSEC. +5 - MOVEI TAKES 269 NSEC. +6 - MOVE FROM AC TAKES 374 NSEC. +7 - MOVE FROM MEMORY TAKES 402 NSEC. +8 - HRR FROM MEMORY TAKES 443 NSEC. +9 - SETOM 0 TAKES 571 NSEC. +10 - JRST TAKES 303 NSEC. +11 - JSR TAKES 642 NSEC. +12 - PUSHJ TAKES 708 NSEC. +13 - ADD FROM MEMORY TAKES 438 NSEC. +14 - MUL (9 ADD/SUB - 18 SHIFTS) TAKES 2.42 USEC. +15 - DIV TAKES 4.70 USEC. +16 - FIX A FLOATING POINT ONE TAKES 874 NSEC. +17 - FLTR AN INTERGER ONE TAKES 881 NSEC. +18 - FAD (1 RIGHT SHIFT) TAKES 1.59 USEC. +19 - FAD (8 SHIFT RIGHT - 3 LEFT) TAKES 1.81 USEC. +20 - FMP (7 ADD/SUB - 14 SHIFTS) TAKES 2.56 USEC. +21 - FDV TAKES 4.82 USEC. +22 - DMOVE FROM MEMORY TAKES 608 NSEC. +23 - DFAD (1 RIGHT SHIFT) TAKES 2.06 USEC. +24 - DFAD (8 SHIFT RIGHT - 1 LEFT) TAKES 2.06 USEC. +25 - DFMP (7 ADD/SUB - 32 SHIFTS) TAKES 4.25 USEC. +26 - DFDV TAKES 8.71 USEC. +27 - CONO PI TAKES ?? NSEC. +28 - CONI PI TAKES ?? NSEC. +29 - DATAO APR TAKES ?? NSEC. +30 - DATAI APR TAKES ?? NSEC. +31 - MOVE TO MEMORY TAKES 573 NSEC. +32 - LOGICAL SHIFT (35 PLACES LEFT) TAKES 439 NSEC. +33 - LOGICAL SHIFT (35 PLACES RIGHT) TAKES 470 NSEC. +34 - LOGICAL SHIFT COMBINED (71 PLACES LEFT) TAKES 743 NSEC. +35 - LOGICAL SHIFT COMBINED (71 PLACES RIGHT) TAKES 745 NSEC. +36 - INCREMENT BYTE POINTER TAKES 924 NSEC. +37 - INCREMENT AND LOAD BYTE TAKES 1.21 USEC. +38 - INCREMENT AND DEPOSIT BYTE TAKES 1.60 USEC. +39 - JFCL TAKES 404 NSEC. +40 - CAI TAKES 272 NSEC. +41 - JUMP TAKES 342 NSEC. +42 - CAM TAKES 376 NSEC. +43 - EQV AC TO AC TAKES 402 NSEC. +44 - EQV MEMORY TO AC TAKES 742 NSEC. +45 - SETOB TAKES 571 NSEC. +46 - AOS TO MEMORY TAKES 539 NSEC. +47 - EXCHANGE AN AC WITH AN AC TAKES 545 NSEC. +48 - EXCHANGE AN AC WITH MEMORY TAKES 706 NSEC. +49 - EXECUTE TAKES 471 NSEC. +50 - BLT MEMORY TO MEMORY TAKES 1.58 USEC. +51 - BLT AC TO MEMORY TAKES 1.48 USEC. +52 - DATAI TAKES ?? NSEC. +53 - DATAO TAKES ?? NSEC. + diff --git a/doc/dvhost.txt b/doc/dvhost.txt new file mode 100644 index 0000000..a3b0852 --- /dev/null +++ b/doc/dvhost.txt @@ -0,0 +1,118 @@ +/* DVHOST.TXT - The special HOST device +*/ +/* $Id: dvhost.txt,v 2.3 2001/11/10 21:24:21 klh Exp $ +*/ +/* Copyright © 1997-1999, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +*/ + +THE "HOST" DEVICE + + This is not a real device, nor is it anything that ever +existed on a real PDP-10. Rather, it is a pseudo-device which serves +as a simple API to request services or information from the emulator. +It is called "HOST" to emphasize that its status or I/O represents +communication with the native host platform that the emulator is +running on. + + +"HOST" DEVICE CONFIGURATION + + As for other devices, the DEVDEFINE command must be used +(typically as a line in "klh10.ini") to associate the HOST device with +either a unibus address (for the KS10) or a PDP-10 device number (for +all other models). This needs to be a device number or address that +is not otherwise used by something else, and any monitor modifications +that refer to this device must use the same number or address. + + KL10 example: + + devdefine idler 700 host ; PDP-10 device 700 + + where "host" specifies the HOST device; "idler" is a name + of your own choosing, and 700 is simply a device number that + does not conflict with any standard DEC devices. + + KS10 example: + + devdef idler ub3 host addr=777000 + + where "host" specifies the HOST device; "idler" is a name + of your own choosing, and "ub3" plus "addr=777000" specifies + a unibus address that should not conflict with any standard + DEC devices. + +There are currently no configuration parameters associated with the HOST +device, and only one such device can be defined. + + +KL10 "HOST" DEVICE USAGE + + Although any of the four basic PDP-10 I/O instructions (CONI, +CONO, DATAI, DATAO) can be used with this device, only one specific +instance is currently meaningful, and its use is very simple. + + IDLE FUNCTION: CONO ,1 + + The emulator process will enter an "idle" state with respect to the + host system, until a PI (priority interrupt) event occurs. During + this state no host CPU time is consumed emulating PDP-10 instructions, + although device subprocesses are still free to run. Instruction + execution will resume with a dispatch to the appropriate PI + interrupt handler, which should eventually return to the next + instruction following the CONO. + +All other I/O instructions will behave as if the device was +non-existent. A CONO with an E != 1 will be a no-op, as will DATAO; +CONI and DATAI simply read a zero word. However, new functions can +obviously be readily added as the need arises. + + +KS10 "HOST" DEVICE USAGE + + To invoke the "idle function" on the KS10, just write the +value "1" to the device's unibus address. The exact IO instruction +used to do this should not matter. + + +MONITOR PATCHES FOR THE "IDLE" FUNCTION: + + In order to effectively use the HOST "idle" function, you must +determine the right place to patch your monitor to use it -- the point +in the scheduler where the system decides there isn't anything it can +do and begins to run the "null job". + +For a TOPS-20 V7 monitor the most appropriate patch is to replace the +instruction at SCDNL1-1 in SCHED.MAC (a JRST SCDNL2) with the "IDLE" +function instruction. In context: + + SCDNUL: SETOM NULJBF + SCDNL2: ... + + JRST SCDNL1 ; Something to do. + JRST SCDNL2 ; Replace with CONO ,1 + ; which will fall through when there's + ; any PI activity. + SCDNL1: + ... + +For a TOPS-10 monitor I would suggest replacing the instruction at +NULCOD+1 in CLOCK1.MAC (a SOJG 6,1) with the "IDLE" function +instruction. However, this has not been well tested and there may be +a more appropriate place. + +You can verify whether this function is working by observing the +process CPU usage on the host system. As long as the KN10 is not +running the monitor's null job, the emulator will be using as much CPU +time as possible -- as much as 95% on an otherwise lightly loaded +single-CPU system. But when the monitor has nothing to run and the +KN10 is effectively idle, the emulator's CPU usage should drop to +perhaps 5%. It will never be completely zero because all monitors +always maintain a clock interrupt that drives periodic updates and +scheduler checks. + diff --git a/doc/history.txt b/doc/history.txt new file mode 100644 index 0000000..0b3b0da --- /dev/null +++ b/doc/history.txt @@ -0,0 +1,426 @@ +/* HISTORY.TXT - KLH10 Historical notes +*/ +/* $Id: history.txt,v 2.3 2001/11/10 21:24:21 klh Exp $ +*/ +/* Copyright © 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +*/ + +The following two messages were sent when the first version of the +emulator was announced. Although I always cringe a bit on reading +things from my younger self, I've included them as historical +documentation because of the glimpse they provide back into a more +innocent time when this was all just terrific fun. No kids, no gray, +no NDAs, no licenses, and no lawyers. + + +Date: Mon, 20 Jul 1992 23:51:04 BST +From: Ken Harrenstien +To: its-lovers@mc.lcs.mit.edu, tops-20@wsmr-simtel20.army.mil +Cc: klh +Subject: One small step, one giant leap +Message-Id: + +On this anniversary of a truly historic moment, I would like to announce +a somewhat smaller event which may loom the larger for being closer to +our hearts. + + stealth.us.oracle.com% telnet nx.us.oracle.com + Trying 139.185.5.134 ... + Connected to mit-nx.us.oracle.com. + Escape character is '^]'. + Unknown ITS PDP-10 + + NX ITS.1645. DDT.1545. + TTY 11 + 2. Lusers, Fair Share = 136% + klh$u + *peek^K! + + NX ITS 1645 Peek 629 7/20/92 23:02:48 Up time = 2:15 + Memory: Free=381 Runnable Total=83 Out=71 Users: High=6 Runnable=1 + Index Uname Jname Sname Status TTY Core Out %Time Time PIs + 0 SYS SYS SYS HANG ? 51 0 0% 4 + 1 CORE JOB CORE UUO ? 0 0 0% + 2 KLH0 HACTRN SYS TTYI T0 30 9 1% + 3 11TLNT TELSER 202405 SLEEP ? 1 0 0% + 4 KLH HACTRN KLH HANG > 30 9 0% + 5 KLH PEEK KLH +TTYBO T11 C 83 71 7% + Fair Share 135% Totals: 195 9% 7 + Logout time = 2 Lost 0% Idle 98% Null time = 2:14 + + + A + NX ITS 1645 Peek 629 7/20/92 23:02:50 Up time = 2:17 + IMP is up. TCP/IP is available. + Ix Usr Uname Jname State RWnd Ibf SWnd ReTxQ Lclprt Fgnprt Fgnhst + 34 3 11TLNT TELSER OPEN 5170 0 10000 0 0 27 10107 139.185.5.5 + 4 buffers (4 free) + + STY Map + Idx STY owner Idx STY user Host + T11 3 11TLNT TELSER 5 KLH PEEK 139.185.5.5 (TCP) + + Q + :KILL + *$$u + NX ITS 1645 Console 11 Free. 23:03:13 + + telnet> q + Connection closed. + stealth.us.oracle.com% + + +Yes, NX is a real, live, internet-host ITS assembled from the final +snapshot of AI. So here's the punch line... + +***There is no KS-10.*** NX is running on a "KLH-10" -- a virtual PDP-10. + +Over the past two months I was finally able to put everything together +to create a complete PDP-10 emulator, including all necessary devices +(RP06, TM03, console, IMP); to distinguish it from a real DEC machine +I'm calling it a KLH-10 per Alan Bawden's suggestion. The current +version is completely in C and should eventually be portable to most +platforms, but at the moment NX's host machine is a 33Mhz SUN Sparc-2 +workstation sitting on my desk. + +It's a long story with many more details and ramifications, all of which +I'll put off for later. Although I wrote it entirely on my own, it would +never have happened without many people who I'd like to thank here: + + Stu Grossman, for convincing me with his KX-10 that a hardware +emulation in C was feasible. + Alan Bawden & Dave Moon, who (apart from their already legendary +roles in ITS) helped as much as they could without actually being there. + Jay Verkler (JLV), who with his group has re-created an AI lab +work environment within Oracle. It's called the Network Products Division +and made this hack possible. + Plus everyone else on these lists whose messages and actions +have helped keep the PDP-10 alive in spirit and reality a little +longer... may it now live as long as we wish! + +--Ken + +23-Jul-92 20:25:01-GMT,15781;000000000001 +Received: by churchy.us.oracle.com (5.59.11/37.7) + id AA19272; Thu, 23 Jul 92 21:24:48 BST +Date: Thu, 23 Jul 1992 21:24:47 BST +From: Ken Harrenstien +To: its-lovers@mc.lcs.mit.edu, tops-20@wsmr-simtel20.army.mil +Cc: klh +Subject: KLH10 info +Message-Id: + +OK, here are some more details about the KLH10. I've been a bit +overwhelmed by the many private responses and hope this answers most of +the questions. + +First, the FAQs I've been getting: + + * "When/How can I get a copy?" + Ouch, probably not until after Interop (Oct). It kills me to say + this, but my time *is* limited. (The usual bribes might help :-) + RMS has suggested distributing it via GNU/FSF, and I like that idea. + + * "Will it run ?" + Anything based on the KS10 should run with minor mods. + The 166, KA and KI will require moderate I/O instruction changes. + The KL10/20 (extended addressing, DTE20, etc) would require + major surgery -- without anesthetic. + + * "How fast is it?" + That depends mostly on the host platform. Using a 33 MHz Sparc-2, + very roughly 0.2 MIPS, perhaps 50% of a KA. + More about this farther on. + + * "Why can't I connect to NX?" + Oracle uses firewalls to prevent full Internet access to their + internal networks. We are trying to set up an ITS turist machine + but it needs to be done very carefully. Wait for an announcement. + + * "Thanks!" + You're welcome!! + +The rest of this message talks about various aspects at more length: +specifics of the target machine & devices, the emulator, and various +other thoughts. It is a bit long for one message, but I figure it's +better to get it all out of the way. My apologies to any uninterested +people. + +Summary: +------- + Target machine: KS10 with MIT ITS microcode + Target config: 512K memory, RH11/RP06, RH11/TM03, FE console, + ACC LH/DH & IMP + Current speed: ~0.2 MIPS + Current host: 33 MHz Sparcstation-2 with internal disk, SunOS 4.1.1 + Compiler: GCC 2.1 + Misc: Sources .5MB, runtime memory 4.3MB, disk lotsaMB + +About the Target Machine: +------------------------ + + The decisions I made while coding the emulator all boil down to +a simple desire to get something useful and interesting up as quickly +as possible. Among other things this meant using an ITS-microcode +KS10 as the target machine, because: + + [1] I don't have official access to TOPS-20 binaries or sources, + and didn't want to worry about licensing hassles. Ditto TOPS-10. + I also am lacking the voluminous documentation about installation + and operating procedures. + + [2] The most recent ITS binaries and sources are all archived + online (thanks to Alan), and all are for the KS10 version. All + documentation is likewise online, and in any case I have a + somewhat more intimate understanding of ITS. + + [3] The KS10 has a simpler architecture than the KL10, which + allows an emulator to run much faster. If not for [2] I might + have started with the KA10 for this reason. + +It is important to realize that the KLH10 is emulating the ITS +microcode, which is slightly different from that for TOPS-10 or +TOPS-20. The primary differences have to do with the pager and the +absence of the extended-instruction set (the noteworthy ITS features +such as one-proceed, JPC, and CIRC are of course supported). + +In order to bring up TOPS-10 or TOPS-20, minor changes would be needed +to the pager code (somewhat more for TOPS-20, owing to its more complex +design). The extended string instructions are ugly but straightforward +for anybody with a strong stomach (and weak mind?). + +Everything else is fully supported, including the double-precision +floating point instructions. I took pains to verify that all results +and PC flags are identical to the real hardware, even for wildly +unnormalized operands. This was *not* one of the fun parts. + +With respect to a KA or KI version, the I/O instructions would also +need to be modified and a number of things changed in the APR and PI +system, but not a great deal. Bringing up TENEX should be relatively +easy given a complete description of the BBN pager. WAITS, however, +would be up to a true SAILor. + +I know many people are going to wish for a KL10 they can run a +full-grown TOPS-20 on. Of course this is theoretically possible, but +it is not going to be easy to get something that runs at a useful speed +without a superfast host. Remember the KL10 is an oversexed mutant +with these strange bulging growths oozing out of random body parts, all +of which have to be duplicated no matter how bizarre. Just dealing +with extended addressing is going to slow down the basic instructions +as well as requiring much more pager overhead; this is one area where +the hardware parallelism is hard to beat. The additional device cruft +(DTE20s, meters, address breaks, etc) doesn't help. Personally I won't +try it without some concrete incentives. + +P.S. Just for grins I included the KA10 and PDP-6 arithmetic ops, +although I don't have a reference machine for verification (ha). Who +knows, maybe a 340 will come along! Spacewar, yeah! + + +I/O Devices: +----------- + + A great deal of the work consisted not of emulating the 10 but +rather emulating a basic set of peripherals. The KS10's use of Unibus +devices made it somewhat more painful than the old I/O device scheme, +because instead of a small matrix of devices and I/O instruction +opcodes, there's a long list of bus addresses to check, each of which +has completely arbitrary meanings. Not to mention the Unibus adapters +with their individual Unibus page maps, all of which are emulated as +well. + +DSK: Currently one RH11/RP06 is supported as a virtual disk. The +actual implementation uses a standard Unix disk file (not a raw disk) +to hold the "RP06" contents; this is so all blocks that haven't been +written will simply not exist (also known as a holey file), thus taking +up much less space than a full raw disk would. Formatting the disk is +obviously unnecessary; sector headers are not written or read. Errors +are pretty much limited to those caused by garbage written into the +registers, so the interface is a bit simpler than the real thing. It's +possible that an OS which can't leave well enough alone will insist on +using some weird maintenance bits, in which case the device code will +need work. Mods for multiple drives or other RH-controlled drive types +are trivial. + +MTA: Similar considerations apply to the RH11/TM03 magtape interface. +>From the "FE" (front-end controller interface) one can mount or unmount +"tapes" that consist of either binary tape images or virtual tapes +built on the fly from lists of unix files. Hooking up a real magtape +drive would probably have been easier, but loading virtual dump tapes +into the filesystem is very fast, actually -- much faster than the real +devices would be. In fact I found one race condition in the ITS +bootstrap loader that required slowing down disk I/O until I was able +to reassemble a fixed version. + +NET: The network interface is an emulation of the ACC LH/DH that some +MIT machines used to have, as well as a virtual IMP. Putting this one +together was a major project in network hacking, not to mention deceit. +Using Sun's NIT (Network Interface Tap) and various trickery, I was +able to set up NX with its own IP address, independent of its +platform's address and thus permitting me to run ITS without +interfering with the other network stuff I'm doing on my workstation. +For efficiency, the virtual IMP is actually a "Simple IMP" that doesn't +bother sending RFNMs, and the virtual LH/DH does I/O in PDP-10 byte +order (not Unibus order) -- this all required changes to the ITS IMP +driver. For a while I considered munging the packets within the +virtual IMP to pretend that the local net was the ARPANET, but finally +decided it was better to fix ITS itself, and did so; ITS can now be +configured for non-ARPA subnets. Geez, I never thought after I did ITS +TCP/IP that I'd be hacking the code again ten years later! + +TTY: The FE console TTY "interface" is emulated, tho the 8080 FE +commands aren't -- no need. There is also a dummy DZ11 driver that +merely reads and writes registers without doing anything. This (and an +equally empty Chaosnet driver) was needed because that's what the last +AI ITS binary image was configured for, and I had to get that up before +I could reassemble a new system (oh the joys of bootstrapping). The +DZ11 won't be hard to finish off, but I'm not sure it's worthwhile; +it's a horribly inefficient device and it's much faster to telnet in +over the network. If realio trulio serial-line I/O is needed, I'd +recommend going for a DH11 if the OS supports it. (It isn't as if +it still costs three times as much as a DZ :-) + + +About the emulator: +----------------- + + The KLH10 is written in C for a vanilla Unix OS, largely so it +can be readily ported to other platforms; in particular, those of the +future as well as those of today. Although re-coding critical sections +in assembler would readily double or triple the basic instruction +speed, it is much easier to simply recompile it on a faster machine. +Although I use the GCC compiler for its efficiency, I don't use any +of its non-standard language constructs, again for portability. + +The most fundamental design decision had to do with the method of +representing 36-bit words on a 32-bit architecture. I wound up coding +around a halfword-based scheme, with each 18-bit PDP-10 halfword +right-justified in a 32-bit host word; thus each PDP-10 word uses 8 +octets. The same format is used for memory, ACs, and disk storage; +basically I traded off space for speed. + +On a machine with a word size larger than 36, or a C compiler that +supported an equivalent integer type (such as double-word ints), many +things become easier, and I'd want to re-do a fair amount of the +arithmetic code. Not because the current version won't work -- it will +-- but because it won't be as efficient. I decided early on that the +differences between optimal code on a 32-bit and +36-bit machine were +just too great to easily combine with the primitive tools available in +C. + +The arithmetic emulation relies only on well-defined native integer +operations; native floating point is never used, both because it is +non-portable (formats vary) and because it is very difficult to +precisely emulate the PDP-10's behavior without actually carrying out +the same internal operations "by hand". This is by far the slowest +part of the KLH10. I checked it out by compiling it on a real 20 +(using KCC, of course!) and running it for hours under a test program +that generated various operand bit patterns and compared the results +with those for real instructions. + +The current implementation is being used as a testbed for threads, and +includes code for both a non-threaded and threaded version. (I +figured, what better test of software parallelism than to emulate +hardware parallelism?) This means that running on a true +multi-processor platform could produce somewhat better performance, +although the main APR execution thread is still serial and will impose +an upper bound on the speedup. Let's not talk about pipelining just +now. + +To clear up one thing that has caused confusion: the emulator is *not* +running standalone on my Sparc. Except for the net device it uses the +usual Unix system calls and burns 100% of the CPU, running flat out. I +don't really notice it since the amount of CPU I use most of the time +is typically a negligible fraction of the Sparc's capability; for +example, I'm writing this note in one window while ITS runs in the +other, and everything's cool. + + +The NEED for SPEED: +------------------ + + In the summary I mentioned a speed of 0.2 MIPS, or about 5 usec +per instruction. This is probably the number people are most avidly +interested in, but at the same time it's also one of the hardest to +measure. PDP-10 speeds have always been tricky, mainly because it all +depends on the precise mix of instructions and operands. Now it's even +fuzzier because so much of the KLH10 is variable. + +Of course, the host platform speed is an overriding factor. But I've +seen that even minor changes in the main loop can produce noticeable +differences in response time, as can the exact nature of memory +references. For example, the SPARC is a register-window machine and +it's faster to pass arguments to functions than to set and read global +variables; it's also faster to use C structure members than globals! +Another machine could have precisely the opposite behavior. I should +add here that the compiler is also tremendously important; using +GCC results in code that is 40% faster than Sun's CC! Yow! + +Anyway, I haven't really buckled down and tried to tune or measure its +performance, so take the 0.2 MIPS loosely. My back-of-the-envelope +calculations before I started suggested that an assembler-coded SPARC +implementation could achieve a KA performance level; the current C +version appears to get perhaps half that speed. Assembling the entire +ITS OS takes about 26 minutes of real time, but I don't know if anyone +remembers how long it took on a real KS10, much less a KA10. + +The Sparc is already a slowpoke compared with some of the new +workstations and chipsets coming out, so it's just a matter of time +before someone cracks the KS10 speed limit on reasonably cheap +personal hardware. I can't help but wonder if might be worth paying +serious attention to a high-speed version, one that would represent a +cost-effective solution for those places still committed to PDP-10 +software. Systems Concepts probably doesn't need to worry -- yet! + + +Future improvements: +------------------- + + The obvious one is speed. Then more machine variants, more +devices. More instrumentation & profiling. A nicely packaged +distribution with your choice of OS and initial filesystem included. +The usual embellishments. Whatever people suggest. + +There's also the possibility of evolving a new virtual machine, one +which realizes more closely whatever the ideal PDP-10 is conceived to +be. (That itself is an interesting question.) For example, the real +KS10 ITS microcode does not actually support JPC, which was a feature +of Holloway's MAC-10 pager; the KLH10 does it trivially and in that +sense is more than a simple KS10. Its pager could support 2048K as +easily as 512K. And so on, all of which implies reconfiguring a real OS +to run on an unreal machine. The first step down this road has already +been taken with NX ITS, assembled to use a non-existent net interface. + +And I've always thought it would be lovely to have a window displaying +a KA-style console panel with a full bank of lights and clickable +switches. I know the KS10 never had one, but that doesn't mean it has +to stay that way, and besides, the KL10 cheated with a 11/40 panel, so +it's down to either the KA or KI. Of course it's just for fun, what +else is this all about? + + +Final thoughts: +-------------- + + I was going to close with the last paragraph, but realized +there's one more thing I want to say. It's just that, um, well, +this will sound silly, but it feels so... weird? ... eerie? +... just plain literally *mind-blowing* to watch this system boot up +and run happily, utterly unaware that it's not on a real machine, or +that anything odd happened since the last time it ran, or that its +earthly incarnation of a noisy roomful of huge cabinets and washing +machines is now entirely self-contained within a small innocuous pizza +box holding up my ITS manuals. Do systems have wathans? I've gotten a +bit more used to it, but every now and then I still sit back, realize +once again what the hell is going on, and hold on to something while +the chills pass. I didn't expect this at all. A side effect of being +imprinted at a tender young age, or something... + +--Ken + diff --git a/doc/install.txt b/doc/install.txt new file mode 100644 index 0000000..38c0ec1 --- /dev/null +++ b/doc/install.txt @@ -0,0 +1,1269 @@ +/* INSTALL.TXT - KLH10 Installation & Configuration +*/ +/* $Id: install.txt,v 2.5 2001/11/19 12:12:06 klh Exp $ +*/ +/* Copyright © 1994-2000, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +*/ + +The first page of this file is a "quick reference" checklist and may +be all you need. If there are any steps you need more help with, see +the appropriate sections that follow. + +The following applies to Unix ports only; procedures and documentation +for other ports such as MacOS or Windows have not yet been written. + +[0] Extract distribution files. + + $ cd + $ gzip -dc .tgz | tar xf - + $ gzip -dc aux.tgz | tar xf - + + Note that there are TWO distribution packages. + The first is the KLH10 Distribution with all KLH10 sources; + the second is the KLH10 Auxiliary Distribution, with useful but + optional non-KLH10 software. + + +[1] Configure native platform (optional). + + Depending on your platform or intended use, you may not need + to do anything here. If you want your KN10 to use the network, + or run most efficiently, see the "Kernel Configuration" section + farther on. + + +[2] Create $KLH10_HOME. + + $ setenv KLH10_HOME /export/home/klh10 ;;; (for example) + $ mkdir $KLH10_HOME + + Create a directory to use as a home for the particular KN10 and + PDP-10 OS you plan to run, and make the environment variable + KLH10_HOME point to it. It's best if this directory holds + nothing but files directly related to runtime operation. + + +[3] Build a KN10 from sources. + + $ cd /bld/ + $ make base-kl ;;; or base-ks or base-ks-its + + Or see the "Building" section farther on. + Read "doc/dfkfb.txt" if you want to do a quick KL performance test now. + + +[4] Install the binaries into $KLH10_HOME. + + $ make install + + If this doesn't work, you can do it by hand; just copy all of the + executable files to $KLH10_HOME (or establish symlinks to them). + + +[5] Create a KLH10.INI startup configuration file (semi-optional). + + If you are just starting out, the easiest thing is to use one of + the sample .ini files to begin with; go on to the next step. + Otherwise, see "KLH10 CONFIGURATION" farther on. + + +[6] Install PDP-10 software. + + At this point, you have a working KN10. However, it will be rather + useless without some PDP-10 software to run on it! + + $ cd /run/klt20 ;;; or klt10, ksits, etc. + $ cp -p * $KLH10_HOME ;;; Copy bootstraps, sample .ini + + Read the appropriate documentation file, one of: + + doc/klt20.txt - KL TOPS-20 + doc/klt10.txt - KL TOPS-10 + doc/kst20.txt - KS TOPS-20 + doc/kst10.txt - KS TOPS-10 + doc/ksits.txt - KS ITS + + Note: By the time you read this there may be auxiliary distributions + available to make this easier, eg to allow booting directly into a + ready-made filesystem. + + Further note: not all configurations are equally well tested. + In order from most to least experience, they are: + KLT20, KSITS, KLT10, KST20, KST10. + This should change as people bang on them. + + +[7] Run. + + $ cd $KLH10_HOME + $ ./kn10-kl [<.ini file>] ;;; or ./kn10-ks + + Normally $KLH10_HOME should be the current directory when the + KN10 starts up, because all default filename references assume this. + + See the file "doc/usage.txt" for command line options, etc. + + +KERNEL CONFIGURATION +==================== + + Installation on most UNIX platforms is straightforward except +for two special requirements of the KLH10 emulator, which sometimes +require kernel reconfiguration. + + [A] Shared memory; MANDATORY if running any device sub-processes. + The so-called SYSV "shm" system calls must be available, and + ideally should allow shared segments of up to 32MB for a KL10, + 4MB for a KS10. The configuration examples below assume a KL10 + since it doesn't hurt even if a KS10 is all you will ever run. + + If this support is lacking, you will get a warning similar to + the following: + [os_mmcreate: shmget failed for 33554432 bytes - Invalid argument] + which is not necessarily fatal, but less efficient. + + [B] Network interface access, if desired. This is extremely + platform-specific. + + +Compaq/DEC Tru64 (Digital Unix, OSF/1) +-------------------------------------- + + The kernel will probably need to be reconfigured; see the +OSF/1 "Guide to System Administration" for the procedure. At least +the following lines must be added to your config file: + + shmmax 33554432 + pseudo-device packetfilter + + The first allows shared memory segments of 32MB. The second + adds support needed for access to the ethernet interface. + (Note: the default value of "shmseg" is 32. This should suffice + unless you plan to define 30 or more emulator disk/tape drives.) + +SUN Solaris +----------- + On Solaris you will almost certainly need to increase "shmmax". + Do this by editing /etc/system to add these 3 lines: + + * Settings needed to run the KLH10 emulator. + * Main memory requires a shared segment of 32MB. + set shmsys:shminfo_shmmax=0x2000000 + +FreeBSD +------- + See the online FreeBSD Handbook chapter on "Configuring the +FreeBSD Kernel" for help, or the similar chapter in the "Complete +FreeBSD" book. At least the following lines must be added to your +config file if they don't already exist (correct as of FreeBSD 4.2): + + options SYSVSHM # SYSV-style shared memory + + # Defaults are too small for KLH10, which needs a 32M segment. + options "SHMMAX=(32*1024*1024)" #max shared mem segment size (bytes) + options SHMMIN=2 #min shared mem segment size (bytes) + options SHMMNI=67 #max number of shared mem ids + options SHMSEG=17 #max shared mem segments per process + options "SHMALL=((SHMMAX/PAGE_SIZE)*SHMMNI)" #max of all shared mem + # Note SHMALL is in PAGES, not bytes! + pseudo-device bpf 1 # Need at least one for NI20 or IMP + pseudo-device tun 1 # Need at least one for IMP + + +Linux +----- + + For at least Red Hat and Debian (2.2 and 2.4 kernels), the +file "/proc/sys/kernel/shmmax" controls the maximum shared memory +segment size. This may already be set to a default of 32M; obviously +it depends on the exact flavor of Linux being used. + +Also, CONFIG_PACKET must be on to enable the PF_PACKET interface. +So far this appears to normally be the case. + + +MacOS 8,9 +--------- + + The KS-ITS version has been ported to MacOS 9 using Code +Warrior to build and "Comm Toolbox" facilities to provide a VT +console. The additional files for this could not be integrated into +the first release in time; if you want them and they are not yet in an +auxiliary distribution, you should be able to find them on +ftp://ftp.its.os.org/its/klh10/. + + +BUILDING FROM SOURCES +===================== + + Connect to the appropriate distribution build subdirectory for your + platform, and type "make " where is one of + the following standard targets: + + Target Builds this emulator + ------ -------------------- + base-kl kn10-kl (KL10B - runs TOPS-10/20) + base-ks kn10-ks (KS10 - runs TOPS-10/20) + base-ks-its kn10-ks (KS10 - runs ITS) + + Let's suppose the platform is FreeBSD-i386 for example: + + $ cd /bld/fbx86 + $ make base-kl + + This will compile everything necessary and leave all of the binaries + in the build directory. The builds are separate from the sources + to make it easier to generate and maintain multiple flavors of KN10s. + +Notes: + +Solaris has two possible Makefiles; the default uses GCC, but you +can change the link to Mk-solsparc-cc.mk which uses Sun's C compiler. + +If there isn't already a likely looking "bld" subdirectory for your +platform, make one using the others as a guide. In general, the +compiler options provided are fairly simple in order to increase the +odds that the compilation will succeed. You can tune or revise the +Makefile for your system by editing a local copy within your +subdirectory, which avoids the need to change any of the actual +Distribution files. When you have it working, let me know what was +done so I can add it to the Distribution or at least to some notes. + + +PORTING +======= + + If you are trying to build things on a platform with no +readily applicable Makefile or "bld" subdirectory, you are essentially +doing a new port. Here are some guides. + +[1] Unix systems are the easiest. Mac has been done for the KS. + A couple of people are eyeing a NT/W2K/XP port but it's not there yet. + +[2] Look at cenv.h to see what is needed and add new names if necessary. + You need to set at least CENV_SYS_xxx and CENV_CPU_xxx and possibly + a few other things. + +[3] A good program to start with is "wxtest". Get that to build and + verify that when run, it passes all of its tests. If it fails, + nothing else will work! + +[4] Then to build a KN10, you should start by trying to build a "synch" + version with no device subprocs. Rig your Makefile so it sets the + appropriate CENV_SYS_xxx and CENV_CPU_xxx flags, and build the + target "port-ks". + +[5] Once that works, move up to the target "base-ks". This is more + complex but still avoids trying to build the networking code, which + is the most unportable part. + +[6] Finally, try "base-kl" or "base-ks-its", either of which will try to + build network code. It's OK if the code doesn't actually work at + first as long as it compiles, since you can still run the KN10 + whether the network device is up or down. + +Good luck. + +There are a hundred compile-time options and a bazillion ways to +permute them. Only a handful of combinations are well tested. In +particular, it is dangerous to mix "synch" and non-synch options. + +If you do need to fix or modify any of the sources, read this file: + + doc/coding.txt - Coding and porting guidelines + +And please let me know about them! You can send bug fixes or ideas, +including suggestions for improving the current build & install +process, to me at . + +INSTALLING BINARIES +=================== + + This is a largely trivial operation of copying all generated +executables over into $KLH10_HOME, but there is one tricky bit. + +If you plan to use the network interface emulation, you MUST either +run the KN10 as root, or make the interface process setuid-root. +There is no single right choice. + +If you want to do the latter: + Make sure only authorized users can access $KLH10_HOME. + Then connect to it, become root (superuser), and do one of: + KL: # chown root dpni20; chmod 4750 dpni20 + KS-ITS: # chown root dpimp; chmod 4750 dpimp + +Another factor to consider is that running as root may allow you to +use the MEM_LOCK setting to lock things in memory and avoid swapping. + +If your KN10 is being automatically started up in the background when +your system boots, running as root is simplest. + + +KLH10/KN10 CONFIGURATION +======================== + + Each KN10 must be configured at runtime in order to do +anything useful. This is done by means of a file called "klh10.ini" +that contains all KLH10 commands you want executed on startup; +normally this includes the complete device configuration +specifications for your KN10. + +The configuration of these devices is by far the most important part +of installation, and the remainder of this file is dedicated to those +issues. + -------------------------- + + Configuring a KN10 is logically very similar to configuring a +real machine. Each KN10 is compiled to emulate a specific CPU and +microcode, with specific devices available for "plugging in" at +runtime. You cannot change the CPU/microcode, but you have some +control over what devices are on your system and how they look to the +virtual KN10. + +As distributed, there are three base machines: + + Target CPU uCode Runs + ------ --- ----- ---- + base-kl KL10B v.442 TOPS-10, TOPS-20 + base-ks KS10 v.130 TOPS-10, TOPS-20 + base-ks-its KS10 v.262 ITS + +The KL10B version provides emulation for the following: + + CPU: KL10B with extended addressing + Memory: 4MW MF20 (8192 pages of 512 words) with MCA25 cache + Microcode: v.442 (supports final versions of TOPS-10 or TOPS-20) + Available Devices: + DTE - one CTY + RH20 - up to 8 (7 if using a KLNI/NIA20) + Disk - 8 RP06 or RP07 drives per RH20 + Tape - 8 TM02/3 formatters per RH20, with one TU45/TU77 each + Network - one KLNI/NIA20 ethernet interface + +The KS10 version provides emulation for the following: + + CPU: KS10 + Memory: 512KW (19 bits - 1024 pages of 512 words) + Microcode: ITS v.262 (supports final version of KS10 ITS) + DEC v.130 (supports final versions of KS TOPS-10/20) + Devices available: + FE - one CTY + RH11 - up to 2 + Disk - 8 RP06 or RP07 drives per RH11 + Tape - 8 TM02/3 formatters per RH11, with one TU45/TU77 each + Network - one "ACC LH-DH" IMP interface (ITS only) + + +There are no runtime configuration parameters for the CPU (future +versions may provide some). Configuration setup for each device is +covered in more detail in the following pages. + +For a KN10 to know about a particular device, the device must be +defined at runtime with the KLH10 "devdefine" command, which normally +is put into the KLH10.INI startup file. + +General format for DEVDEFINE (non-KS): + + DEVDEFINE + + - A short (6-character) name of your choice to + uniquely identify this device. Examples might be + dsk0, dsk1, mta0, xyzzy, etc. These names are meaningful + only to you; the KLH10 parser doesn't care, nor are they + passed to the KN10 in any way. + + The is how you can refer to the device + when using other KLH10 commands such as DEVMOUNT. + + - The KN10 device number you are binding. + For all machines but the KS10, this is normally + an octal device number such as would be used in + I/O instructions (DATAI/DATAO, etc). For example, + use 200 for DTE#0, 540 for RH20 #0, and so on. + + The is how the KN10 can refer to the device. + + - Specifies what the device actually is, by + identifying the "emulator driver" you are binding + this device to. The driver is a software module that + is invoked to support all references to the device, + whether from you or from the KN10. + + Giving the "devshow" command will list all of the known + drivers (currently DTE, RH20, RP, TM03, NI20). External + (dynamically linked) drivers are supported but untested. + + - Various parameters specific to the + device driver type, usually optional. Their syntax is + normally of the form =, but sometimes + just the is sufficient. + The ordering of these parameters does not matter; nor + does the case of the keywords. + + +General format for DEVDEFINE (KS): + + Similar, but some of the devices are different and the + DEVDEFINE command has a slightly different format. + The KS10 introduced a "new" IO instruction scheme whereby all + devices were actually on a PDP11-type Unibus; the old-style + IO instructions such as DATAI do not exist as such, so there + are no device numbers. + + DEVDEFINE + + - Same; a short name of your choice. + + - The Unibus you are binding this device to. + This must be either "ub1" or "ub3" for Unibus #1 or #3 + respectively; there are no other choices. + (The KN10 can easily support more, but it would no longer + be a kosher KS10.) + + - Same; the emulator driver name. + The recognized KS10 devices are: + RH11, RP, TM03, LHDH, CH11, DZ11 + (The last three are used only with ITS.) + + - Same, with one important addition. + All Unibus devices MUST specify the following parameters: + + addr=<#> - The octal Unibus address of device + vec=<#> - The octal interrupt vector address + br=<#> - The BR interrupt level, one of 4,5,6,7 + + +In the following discussions of each device driver, all of the parameters it +understands are listed and described. + +DTE (CTY) CONFIGURATION: (KL only) + + The current DTE emulation does not support any terminals other +than the CTY. Configuration normally consists of the following command: + + devdefine dte0 200 dte master + +The parameters are: + +[MASTER=] Default: FALSE +[MASTER] Same as MASTER=TRUE + Specifies whether this DTE is the "master" DTE. Since only the CTY + is currently supported, only one DTE is ever needed, and this should + always have MASTER specified. + + +[ACKDLY=<#msec>] Default: 0 + This specifies a delay, in msec, between the time the DTE receives some + data for output and the time it returns an ACK to the KN10. + + For TOPS-10 you should set ackdly=5. The TOPS-10 monitor is + unprepared to deal with the instantaneous response of the KN10 DTE + and suffers from the resulting race condition; setting this value + to some "reasonable" amount keeps CTY output streaming smoothly. + + TOPS-20 doesn't appear to need any delay. + +[WARN=] Default: FALSE +[WARN] Same as WARN=TRUE + Specifies whether to show warnings for unrecognized DTE commands, + which are otherwise ignored. A milder variant of debugging output. + +[DEBUG=] Default: FALSE +[DEBUG] Same as DEBUG=TRUE + This can be used to turn on debug tracing as soon as the device + is created, without requiring a separate DEVDEBUG command later. + +RH20 CONTROLLER CONFIGURATION: (KL only) + + Before you can define any devices that use the Massbus, you must first +define a RH20 Massbus controller for them. The commands for this are: + + devdefine rh0 540 rh20 ; Controller #0 + devdefine rh1 544 rh20 ; Controller #1 + devdefine rh2 550 rh20 ; Controller #2 + devdefine rh3 554 rh20 ; Controller #3 + devdefine rh4 560 rh20 ; Controller #4 + [rh5 564 normally RESERVED for NI20] + devdefine rh6 570 rh20 ; Controller #6 + devdefine rh7 574 rh20 ; Controller #7 + +The RH20 driver emulates both the RH20 controller and its associated data +channel. It works in the same way whether the drives it is controlling +are disk or tape devices. + +There are currently no parameters for the RH20 driver. + +The DEVDEFINE command is used to define and bind a device to a controller. +When used in this way, the specification is a bit different: + + - If the device is actually a drive unit of a controller + and does not have its own full-fledged KN10 device number, + the form becomes: + + .<#> - where identifies a previously defined + controller, and the <#> specifies a unit number. + + For example: + devdefine rh0 540 rh20 ; KN10 device 540 + defdefine dsk0 rh0.0 rp ; Ctlr RH0, unit #0 + + For more examples, see the configuration commands for the Disk + or Tape devices. + +NOTES: + + The important thing to be aware of about RH20 configuration is that +only *ONE* I/O transfer can be in progress at any time on a particular RH20, +no matter how many drives it is controlling. In order to have multiple +transfers in progress, each must be taking place on a different RH20. + + On a real KL, extra RH20s would cost extra, so systems with up to 8 +drives might put them all on one controller. On a KN10, all RH20s are +"free", so you can use as many as you like. However, don't blindly define all +8 Massbus controllers. If you are using the network interface then the #5 +slot must be reserved for the NI20! + + The degree of parallelism is of course limited by the actual physical +hardware the KN10 is running on; inventing multiple RH20s does not magically +create new disk heads or I/O buses. If your virtual disks are all on the same +physical disk, only one of them can be doing a real I/O transfer regardless of +whether they are controlled by different RH20s. On the other hand, different +RH20s do allow multiple I/O requests to be given to the native OS, which can +then select and queue them in the most efficient manner. + +WARNING: + + TOPS-10 does not support both tape and disk drives on the same +RH20! Each RH20 must either control disks only, or tapes only. + + TOPS-20 however does support mixed configurations, and it is safe +to assign both tapes and disks to the same RH20 if you wish to do so. + +RH11 CONTROLLER CONFIGURATION: (KS only) + + Before you can define any devices that use the Unibus, you +must first define a RH11 Unibus controller for them. As far as is +known, all KS10s had exactly the same RH11 configuration, and the +commands for this are: + + devdef rh0 ub1 rh11 addr=776700 br=6 vec=254 + devdef rh1 ub3 rh11 addr=772440 br=6 vec=224 + +As for the RH20, there are no other parameters besides the generic +Unibus ones illustrated above. + +RP (DISK) CONFIGURATION: (KL & KS) + + A RH20 or RH11 controller must be defined before you can +define a disk drive, since the drive expects to be bound to a +controller. + + An example of defining a RH20 and two RP drives looks like this: + + devdefine rh0 540 rh20 ; Define controller #0 + devdefine dsk0 rh0.0 rp type=rp06 path=RH20.RP06.0 + devdefine dsk1 rh0.1 rp type=rp07 path=/dev/rrz3a format=rare + +The parameters for disk configuration come in two groups: those that describe +the DRIVE, and those that describe the PACK mounted on the drive. + +The following are DRIVE configuration parameters: + +[TYPE=] Default: RP06 + This specifies the drive's type, which determines its geometry (size) + and how it will look to the KN10. The following types are known: + RP04, RP05, RP06, RP07 + RM02, RM03, RM05, RM80 + The RP06 and RP07 are the recommended choices; the others have not + been tested. + This parameter will also accept an octal number to specify an arbitrary + device type between 0-777 inclusive. If this feature is used, all + geometry values are cleared and all of the CYL, TRK, and SEC parameters + must be given in order to completely define the drive. + [See the note below about "Dynamic Geometry" drives.] + +[CYL=<#>] Optional: (range 1-65536 inclusive) +[TRK=<#>] Optional: (range 1-256 inclusive) +[SEC=<#>] Optional: (range 1-256 inclusive) + Use these parameters only when defining the geometry for a non-standard + drive. All arguments are decimal numbers. + CYL is the number of cylinders (including maintenance cyls). + TRK is the number of tracks (or heads) per cylinder. + SEC is the number of 128-word sectors per track. + The presence of any of these parameters flags the drive as a "dynamic + geometry" drive; however, it is not necessary to specify them all if + a preceding TYPE parameter has already set the default geometry values. + [See the note below about "Dynamic Geometry" drives.] + +[SN=<#>] Default: + This is whatever 4-digit drive serial number you would like + the drive to have. TOPS-20 thinks that two drives with the same + SN constitute a dual-ported drive, so these numbers must be unique. + If no SN is specified, the KN10 will invent a safe one for you. + +[DPDMA=] Default: TRUE + This is primarily for debugging; it controls whether the device + process (DP) should do I/O directly to and from KN10 memory, + if that is possible. + Unless there is some reason to suspect bugs, this should always + be left TRUE. + +[DPPATH=] Optional: dprpxx + Specifies where, in the native OS filesystem, to find the executable + program for the device process. + Normally this is "dprpxx" in the KLH10_HOME directory, but may be + changed for debugging. + +[BUFSIZ=<#>] Optional: 512 + This is another debugging variable that allows setting the size + of the device's internal buffer, which is used if format conversion + is needed or for transfers that are not a multiple of the sector + size. Don't use this. + +[DEBUG=] Default: FALSE +[DEBUG] Same as DEBUG=TRUE + This can be used to turn on debug tracing as soon as the device + is created, without requiring a separate DEVDEBUG command later. + +[DPDEBUG=] Default: FALSE +[DPDEBUG] Same as DPDEBUG=TRUE + This can be used to turn on debug tracing as soon as the device + process (DP subproc) is created, without waiting to give a + DEVDEBUG command. + + +The following are PACK mount parameters: + +[PATH=] Default: RH20..<#> (e.g. RH20.RP06.0) + Specifies the pack location -- the filename of the virtual disk + in the native OS filesystem. This can be either a normal file + such as "RH20.RP06.0" or a raw disk partition device such as + "/dev/rrz3a". + For ease of maintenance, a normal file is recommended. The + file does not have to exist; it will be created if necessary. + +[FORMAT=] Default: RAW + Specifies the pack format -- how the data in the virtual disk + file is organized into PDP-10 words. There are several possible + formats, most of which exist only to help port virtual disks between + dissimilar native platforms. The most useful formats are: + RAW - the fastest. 1 word in 8 bytes; no I/O conversion. + DBD9 - the most compact. 2 words in 9 bytes. + RARE - A "semi-raw" mode that writes in RAW but cleans + data on read; suitable for un-initialized packs. + Note: RAW mode must be used with care, as the initial disk contents + must be completely initialized to zero. + +[RO] or [RW] Default: RW + Specifies whether the pack is Read-Only (RO) or Read/Write (RW). + + +NOTES: + + The pack pathname will be created if it doesn't exist. I suggest you +make this be a symbolic link either to a raw disk partition large enough to +represent a disk of the selected type, or to a normal file in some part of the +filesystem that likewise has enough free space available. + + Note that if using normal files, you can get by with less +space for a while because only sectors that are actually referenced +will have storage allocated for them in the native filesystem. +However, once a sector is written, it can never be de-allocated; so +over time as the monitor runs and dribbles on the virtual disk, you +will slowly approach the full space requirements of the emulated disk. +If you run out of space on the native filesystem that holds such a +normal file, the emulated disk will appear to get an I/O error and +will go unsafe. It is best if you don't let this happen, by ensuring +that (1) the native filesystem has enough space to begin with, and (2) +this space isn't consumed by other things. + + The actual space requirements depend on the drive type and format you +select. Note in the following table that one sector is 128 PDP-10 words. +Actual sizes are rounded up to the nearest MB. + + T20 Format= Format= + Drive # Sectors Pages RAW/RARE DBD9 + -- 1 - 1024 bytes 576 bytes + -- 4 1 4096 bytes 2304 bytes + -- 245 1 MB + -- 435 1 MB + -- 1,000 4.1 MB 2.3 MB + RP05 156,180 39,045 160 MB 90 MB + RP06 309,700 77,425 318 MB 179 MB + RP07 866,880 216,720 888 MB 500 MB + + Note you can select "dbd9" format for the maximum packing +density, at the price of a slightly slower system since the KN10 then +has to do an extra data conversion step for all disk I/O, and virtual +sectors are no longer aligned with physical disk blocks. + + Other disk types are available with different sizes, but I'd recommend +using some combination of RP06s and RP07s. Note that you probably can't pack +them neatly into existing native OS drive partition tables, so some space will +be wasted unless using normal files. + + Normal files are expected to be slightly slower than raw disk +partition devices, since they are subject to filesystem overhead and +kernel buffering. + + Raw disk partitions require some special care. First, they +can *ONLY* be used when "raw" or "rare" format is selected. Second, +if specifying "raw" format, the disk partition must be completely +cleared before its first use. This can be accomplished by, for example, +something like the shell command "dd if=/dev/zero of=/dev/rrz3e". + + The DEVMOUNT and DEVUNMOUNT commands can be used to mount and +unmount virtual packs from a drive, with the additional optional +parameters "RO", "RW", or . Note however that this feature has +not been extensively tested and a typo in the command can result in +a very badly confused PDP-10 OS. + + +NOTE: DYNAMIC GEOMETRY + [WARNING: USING THIS FEATURE REQUIRES MONITOR MODIFICATIONS!] + + This is a feature of the emulator that allows the +specification of drives other than those which historically existed, +normally for the purpose of using much larger virtual drives. + For each drive, the emulator has a geometry table of CYL, TRK, +and SEC sizes. Specifying any known standard disk type sets these +values to the correct ones for that type; however, if this is followed +by any of the CYL, TRK, or SEC parameters, the corresponding value is +updated and the drive is flagged as having a "dynamic geometry". Note +that specifying an arbitrary device type resets all of these values to +0, necessitating explicit specification of all three. + + This feature MUST NOT BE USED except with a monitor that has +been modified to know about the new geometry. This can be done in +either or both of two ways: + + (1) Invent a new drive type and modify the monitor's + predefined geometry tables accordingly. Types 030-040 + inclusive are available -- they are part of the range + considered "RP04 type" but were never used by any real + drives as far as is known. + + (2) On initialization, check the 01000 bit of the Drive Type + register (Reg 06 for the RH20, 013 for the RH11), which + the KN10 sets to indicate a "dynamic geometry" drive. + If set, read the EPOS and EPAT registers to obtain the + drive geometry. + +The second method is preferable because it is the most flexible. A +dynamic geometry drive can always be examined by the monitor to +determine its size at startup by reading the "ECC Position" and "ECC +Pattern" registers (016 and 017 for the RH20, 022 and 023 for the +RH11). + +To summarize: + New "Drive Type" flag in register 06: + Bit 01000 set to indicate "dynamic geometry". + This bit was never used as far as can be determined. + Re-use registers: (only if above flag is set) + 016 (ECC Position) returns "Max Cylinder", range 0-177777 + 017 (ECC Pattern) returns "Max Track/Sector", 0-377 and 0-377 + Track is high 8 bits, Sector is low 8. + +Note that the "Max" values indicate the maximum possible value of the +field, which is one less than the number of things. Thus a max sector +value of 077 means you can address 077+1 => 0100 sectors per track +(corresponding to SEC=64). Also note that the vanilla T10/T20 +monitors use a DPB pointer with only 5 bits for the track field, so +unless that is modified you should not specify a TRK higher than 32. + +Although new registers could have been used (024-037 are available) it +was deemed more convenient to re-use the EPOS and EPAT registers, +which are otherwise never used by the KN10. + +The reason for using a Drive-Type flag rather than simply relying on a +new drive type value is to allow for retaining existing drive types +(e.g. RP07) so that the bulk of existing monitor code can be left +unchanged. It can be hard to find everything that depends on the +drive type since not everything is table-driven, and bootstrap code +for example is also affected. + +So, for example, one can set the drive type to RP07, accept the RP07 +defaults of 32 tracks and 43 sectors so that bootstrap code will work +without change, and then increase the number of cylinders in order to +achieve much larger disk sizes. For example: + + devdefine dsk1 rh0.1 rp type=rp07 cyl=65536 path=bigdisk.1 + +would result in a virtual disk of: + + 65536 cyl * 32 trk * 43 sec = 90,177,536 sec = 52GB. + +TM03 (TAPE) CONFIGURATION: (KL & KS) + + On a real machine, a tape transport ("slave") is controlled by +a "formatter" such as the TM03, which in turn is controlled by a +controller (RH20 or RH11). Up to 8 slaves can be controlled by a +single formatter, and up to 8 formatters by a single controller. + + The KLH10 code emulates this model, except that for simplicity +a formatter controls only one slave; unlike the physical hardware, in +a KN10 there is no cost penalty for additional formatters. + + NOTE! It is best to bind tapes and disks to different +controllers, for two reasons. First, TOPS-20 can handle mixed +devices, but TOPS-10 cannot; on TOPS-10 a RH20 should only have disks, +or tapes, but not both. Second, only one device on each controller +can be actively transferring data at a time, and tapes are usually +much slower than disks. + + As for disk, the formatter must be bound to a controller when +defined. An example of defining a RH20 and one TM03/TU45 looks like +this: + + devdef rh1 544 rh20 + devdef mta0 rh1.0 tm03 fmtr=tm03 type=tu45 path=/dev/rmt0a + +The possible parameters for the TM03 driver are: + + +[FMTR=] Default: TM03 + Specifies formatter device type. + Only two types are available: TM02 and TM03. Normally TM03 should + be used. + +[TYPE=] Default: TU45 + Specifies transport (slave) device type. + Only three types are available: TU45, TE16, and TU77. + They differ slightly in the features the KN10 will think are + available, but none matter to the emulated tape. + +[SN=<#>] Default: + This is whatever 4-digit drive serial number you would like + the transport to have. As far as is known, nothing depends on this. + +[PATH=] Default: + Specifies the hard tape device to bind to the transport, with + the default parameters "RW" and "HARD". This should be the + raw tape device, such as "/dev/rmt0a". Do not use either the + non-raw (/dev/mt0a) or non-rewind (/dev/nrmt0a) device names! + + If this is not specified, nothing is "pre-mounted". The mount + state can be changed at any time with DEVUNMOUNT and DEVMOUNT. + + Note this cannot be used for mounting virtual tapes; use the DEVMOUNT + command instead. + +[DPPATH=] Default: dptm03 + Specifies the native OS pathname for the DP (Device Process) program. + +[DEBUG=] Default: FALSE +[DEBUG] Same as DEBUG=TRUE + This can be used to turn on debug tracing as soon as the device + is created, without requiring a separate DEVDEBUG command later. + +[DPDEBUG=] Default: FALSE +[DPDEBUG] Same as DPDEBUG=TRUE + This can be used to turn on debug tracing as soon as the device + process (DP subproc) is created, without waiting to give a + DEVDEBUG command. + + +NOTES: + For backward compatibility, the drivername "tm02" is treated as +an alias for "tm03". + + The DEVMOUNT and DEVUNMOUNT commands can be used to mount and +unmount either virtual tapes or physical tape drives after +configuration, with various optional parameters. In particular, +"HARD" must be specified if the pathname is that of a physical tape +drive. + +NI20 (NETWORK) CONFIGURATION: (KL only) + + The NI20 driver emulates a KLNI/NIA20 ethernet interface and +requires the host system to provide a real ethernet port, either +dedicated for the KN10's use or shared with the native OS. + +The NI20 on a real system is always installed in massbus slot #5, +which corresponds to device 564. So, an example of defining a NI20 +looks like this: + + devdefine ni0 564 ni20 ipaddr=10.0.0.51 + + +The parameters for the NI20 driver are: + + +[DEDIC=] Default: FALSE + This is the most important configuration decision. + Ideally you will have at least two ethernet ports and can dedicate + one of them for the sole use of the KN10. If so, set DEDIC=TRUE + and specify the interface with IFC= (see below). + + If this is not possible (only one interface available) then the + interface must be "shared" with the native OS, rather than being + dedicated. Some things will work; others may not. You must specify + whether you want to use DECNET, or IP, or both. + +[IFC=] Default: + If DEDIC=TRUE is set, or the system has more than one hardware ethernet + port, the interface should be explicitly specified with IFC= so you + get the right one. + However, for a shared interface this will default to whatever the host + system uses for its IP datagrams, normally "ln0" or "tu0" for + OSF/1. + The shell commands "ifconfig -a" or "netstat -i" will show the + known interfaces. + See also the ENADDR parameter. + +[ENADDR=] Default: + Normally unnecessary. + Used to specify the ethernet address to use, if the KN10 has trouble + determining it. Syntax is six hexadecimal values (bytes) separated + by colons. + This value will be ignored if the KN10 can in fact determine the + real address itself, except for Solaris. + WARNING: On Solaris, setting this will attempt to modify the + actual ethernet address of the hardware interface! This is + likely to be required when multiple interfaces exist due to the + brain-damaged way Solaris handles them. + +[DECNET=] Default: FALSE + This flag is only meaningful for a shared interface; it is ignored for + a dedicated interface. + A shared interface will not receive DECNET packets unless you set + DECNET=TRUE. This should not be done lightly as it causes several + risky things to happen, which can disrupt service to the native OS: + + -- The 10 is allowed to change the interface address! + This may confuse other software. As a feeble protection against + shooting yourself in the foot, this is only allowed if the existing + interface address is not already a DECNET address (if it was, that + would imply that the native OS wants to speak DECNET too). + + -- The driver attempts to grab all DECNET packet types! + If the native host is trying to use DECNET itself, this will cause + great confusion. + + -- The 10 is allowed to mess with the multicast addresses of the + interface (adding and deleting entries). + +[LSAP=<#>] Default: + This flag is only meaningful for a shared interface; it is ignored for + a dedicated interface. + + A shared interface will not normally receive 802.3 packets unless you + set the LSAP parameter to the specific source and dest LSAP (DSAP/SSAP) + that you wish to see; for example, "lsap=0xA4". + +[IPADDR=] Default: + This flag is only meaningful for a shared interface; it is ignored for + a dedicated interface. + + A shared interface will not receive IP packets unless you set + IPADDR to the IP address that the PDP-10 OS expects to use. + This should not disrupt service to the host OS as long as the IP + address is correctly selected and is different from that of the + host OS. + +[DOARP=] Default: TRUE + Only valid for a shared interface where IPADDR= is also specified. + DOARP says whether the driver should attempt to support ARP itself; + because it is always TRUE where applicable, this parameter need only + be given when turning ARP support off for debugging, either completely + or selectively. + + This special support is needed to compensate for an OSF/1 bug; + OSF/1 either cannot see or ignores ARP packets sent to it from + a packetfilter (unlike IP packets). DOARP attempts to remedy + this with three distinct tactics, all of which are invoked by + the default value of TRUE (bit=01): + + (bit=02) Allows other platforms to find the KN10's address, + by installing a "published" entry in the kernel's ARP table. + This lets the native platform proxy-answer any broadcast requests + for the KN10's address. Necessary because TOPS-20 sometimes + screws up its ARP replies; this also means the packetfilter + only has to pass ARP replies, not ARP requests). + + (bit=04) Allows the native platform to find the KN10's address, + by installing a "permanent" entry in the kernel's ARP table. + Necessary because OSF/1 loopback omits ARP packets, i.e. the + KN10 never sees ARP requests from the native platform. + + (bit=010) Allows the KN10 to find the native platform's address. + Checks outgoing ARP requests from the KN10; if it spots one + attempting to locate the native host, it drops the packet + and substitutes an ARP Reply with the appropriate information + which gets fed back into the KN10 via the receive side. + Necessary because OSF/1 loopback omits ARP packets, i.e. + native platform never sees ARP requests from the KN10. + + You can show the ARP tables with the shell command "arp -a". There is + no cleanup when the DPNI20 process dies; if you want to clean up the + table yourself, use "arp -d " while root. + + Note: None of this ARP hackery is needed if the interface is + dedicated or if no IP traffic is desired. + + +[C3DLY=<#>] Default: 5 + REQUIRED for TOPS-10! + Sets number of milliseconds to delay the NI20 LDPTT operation + while the KN10 continues running, to avoid a monitor race condition. + A good value is probably 5, but may vary with hardware. + The TOPS-20 monitor does not have this bug, but setting this parameter + to 0 probably won't make anything faster. + +[DPDELAY=<#>] Default: 5 + REQUIRED for TOPS-10 and TOPS-20! + Sets number of seconds to delay the KN10 itself when the NI20 + first starts up, again to avoid a monitor race condition. + A good conservative value is 5. If the TOPS-20 monitor complains + "NIA10 initialization timed out" and there are no other obvious + errors, try increasing this. On TOPS-10 the message may say + something about a "KLNI microcode" load failure. + A smaller value will make startup faster, at the risk of tickling + this monitor bug. Every system will be different. + +[DPPATH=] Default: dpni20 + Specifies where, in the native OS filesystem, to find the executable + program for the device process. + Normally this is "dpni20" in the KLH10_HOME directory, but may be + changed for debugging. + IMPORTANT: This program must run as root, either by setting the binary + to setuid-root or by running the KN10 while logged in as root. + It cannot perform the necessary network operations otherwise. + +[ECHOCHK=] Default: TRUE + Primarily for debugging. + Specifies whether to suppress "echo" (self-addressed) packets. + A real KL never sees its own transmissions, so this is normally TRUE, + but on some platforms the actual hardware or native OS already + prevents this from happening so the overhead is unnecessary. + This parameter need only be given when turning off the extra check. + +[ECHOBUF=<#>] Default: 60 + Primarily for debugging. + Number of packets to remember in a ring buffer when ECHOCHK is true. + +[ECHOTMO=<#>] Default: 1 + Primarily for debugging. + Number of seconds to remember packets when ECHOCHK is true. + +[BACKLOG=<#>] Default: + Primarily for debugging. + This specifies the number of packets that OSF/1 will allow + to pile up inside the kernel while waiting for the KN10 to read them. + If you are experiencing some problems with lost packets and it doesn't + appear to be the fault of the network, you could try setting this + parameter, although it is just one of many factors. + +[RDTMO=<#>] Default: 0 + Normally unnecessary. + Sets the number of seconds to time out while doing a read() from + the native system's packetfilter interface. This was needed on + earlier versions of OSF/1 which couldn't kill the "dpni20" process + otherwise (the problem symptom is the persistence of that process + after the KN10 itself has been killed). If needed, try rdtmo=2. + +[DEBUG=] Default: FALSE +[DEBUG] Same as DEBUG=TRUE + This can be used to turn on debug tracing as soon as the device + is created, without waiting to give a DEVDEBUG command. + +[DPDEBUG=] Default: FALSE +[DPDEBUG] Same as DPDEBUG=TRUE + This can be used to turn on debug tracing as soon as the device + process (DP subproc) is created, without waiting to give a + DEVDEBUG command. + + +NOTES: + + * Before bringing up a KN10 as a network host, you will need to carry +out all the usual procedures needed to register a host on your local network. +The fact that the "host" will be virtual rather than physical may be hard to +explain to bureaucratic minds; you don't need to try. Just pretend it's for +real. + + * If you plan to use IP, you'll need to configure or build your PDP-10 +monitor so that it knows about its assigned IP address, knows what network +it's on with what subnet mask, knows its default prime gateway, and whatever +else a real system needs in the way of configuration. + + * The "dpni20" binary must be made setuid-root. I can't +guarantee that this program has no security holes, which is a good +reason to keep the KLH10_HOME directory well-protected. The only +alternative is to run the entire KN10 while logged in as root, which +you may wish to do anyway to enhance responsiveness (it is then able +to lock itself in memory and raise its process priority). + + * Each time the KN10 starts or stops its NIA20, the dpni20 +process is created and killed. This means you can sometimes recover +from problems, or try a new version of the dpni20, simply by using +some mechanism in the PDP-10 OS to stop and "reload" or restart the +NI. On TOPS-20 this is done with the KNILDR program. + + * The most common problem with a shared interface is an +inability for the virtual host (KN10) to talk with the native host; it +depends on whether the native OS allows outbound ethernet packets to +be seen by other input processes, or vice versa. Some platforms allow +this (OSF/1), others don't (Solaris, Linux), and some used to but were +mistakenly "fixed" to disallow it (FreeBSD). + A simple but annoying workaround is to first telnet to some +other host before telnetting back into the KN10. A better solution +would be to fix those OSes that are open-source, and push to have +these fixes incorporated in the standard releases. + +LHDH (IMP): (KS-ITS only) + + This is the big one for a KS10 ITS system. As far as is +known, no DEC operating system for the KS10 provided a TCP/IP network +interface (I believe there was an ARPA version that supported the +older NCP protocol). + +The LHDH driver emulates an ACC LH-DH IMP interface. Note that this +really was an IMP interface, not an Ethernet interface, thus the only +I/O possible is in terms of IP datagrams rather than ethernet packets. + + A typical definition will look like this (line-continued for + clarity only; the KLH10 parser won't recognize \ for that purpose): + + devdef imp ub3 lhdh \ + addr=767600 br=6 vec=250 \ ; Generic Unibus params + ipaddr=10.2.0.6 \ ; ITS IP address + gwaddr=199.34.53.50 ; Host platform's IP address + +The parameters for the LHDH driver are: + +[ADDR=<#>] Required (normally 767600 for LHDH) + This is the starting address for the device's Unibus registers. + +[BR=<#>] Required (normally 6 for LHDH) + This is the BR (Bus Request) priority level to use for interrupts + by this device. + +[VEC=<#>] Required (normally 250 for LHDH) + This is the interrupt vector address to use for interrupts by this + device. + +[IPADDR=] Required + Must always be given, regardless of whether the IMP device is + sharing the host platform's ethernet address or using a dedicated one. + Must correspond to the IP address that the ITS system thinks it has. + For grins, the original ITS systems were: + mit-dms 10.1.0.6 + mit-ai 10.2.0.6 + mit-ml 10.3.0.6 + mit-mc 10.3.0.44 + (RFC 846 lists these and many other historic IP addresses) + +[GWADDR=] Required + Must always be given. This tells the IMP where to send packets + that are not addressed to the same network as the ITS system + (as defined by the IPADDR parameter). + Normally the host platform can be specified, but a real gateway + is OK too. + +[DEBUG=] Default: FALSE +[DEBUG] Same as DEBUG=TRUE + This can be used to turn on debug tracing as soon as the device + is created, without waiting to give a DEVDEBUG command. + +[DPDEBUG=] Default: FALSE +[DPDEBUG] Same as DPDEBUG=TRUE + This can be used to turn on debug tracing as soon as the device + process (DP subproc) is created, without waiting to give a + DEVDEBUG command. + +[DPPATH=] Default: dpimp + Specifies where, in the native OS filesystem, to find the executable + program for the device process. + Normally this is "dpimp" in the KLH10_HOME directory, but may be + changed for debugging. + IMPORTANT: This program must run as root, either by setting the binary + to setuid-root or by running the KN10 while logged in as root. + It cannot perform the necessary network operations otherwise. + +[IFC=] Default: + Normally unnecessary. + Because the IMP interface only looks for IP datagrams, it does not + need to have an ethernet device dedicated to it; whatever it uses + is always shared. + Thus, this will default to whatever the host system uses for its IP + datagrams (the shell commands "ifconfig -a" or "netstat -i" will + show the known interfaces). + On FreeBSD this currently defaults to a /dev/tun<#> device. + +[ENADDR=] Default: + Normally unnecessary. + Used to specify the ethernet address to use, if the KN10 has trouble + determining it. + +[DPDELAY=<#>] Default: 0 + Recognized but currently unimplemented, as ITS is correctly + coded to have no race conditions associated with its IMP. + +[RDTMO=<#>] Default: 1 + Sets the number of seconds to time out while doing a read() from + the native system's packetfilter interface. The default value of 1 + may no longer be necessary but doesn't hurt. + Same as NI20's RDTMO parameter. + +[BACKLOG=<#>] Default: 0 + Primarily for debugging. + Same as NI20's BACKLOG parameter. + +[DOARP=] Default: TRUE + Same as for NI20. + +[DEDIC=] Default: FALSE + Recognized but unimplemented. + +DZ11 (TTY MUX): (KS only) + + This is actually a dummy device which exists to make it possible + to boot and run OS binaries that expect to find a DZ11 in a + specific place. + + The generic Unibus parameters must be provided in the DEVDEFINE so + that this device will respond to IO instructions addressed to it, + but no TTY I/O actually happens. + +CH11 (CHAOSNET): (KS only) + + This is another dummy device, only for ITS. + As for the DZ11, the generic Unibus parameters must be provided. + +HOST (Native Host): (KL & KS) + + There are two variants of this special device, one for each +basic device style: + + ; KL10 example + devdefine idler 700 host ; PDP-10 device 700 + + ; KS10 example + devdef idler ub3 host addr=777000 + +See the file "dvhost.txt" for more details on the use of this device. + +There are no parameters, beyond those for setting the Unibus address +on a KS10. + +; Sample KLH10.INI file: + +; This is a sample KLH10.INI -- the config file that is read and executed +; by default at startup. + +; Define basic device config - one DTE, one disk, one tape +devdef dte0 200 dte master ackdly=5 +devdef rh0 540 rh20 +devdef rh1 544 rh20 +devdef dsk0 rh0.0 rp type=rp06 format=raw path=RH20.RP06.0 +devdef mta0 rh1.0 tm03 fmtr=tm03 type=tu45 + +; Define shared KLNI with address that TCP/IP monitor expects +devdef ni0 564 ni20 ipaddr=10.0.0.51 diff --git a/doc/kldiff.txt b/doc/kldiff.txt new file mode 100644 index 0000000..608b2bc --- /dev/null +++ b/doc/kldiff.txt @@ -0,0 +1,191 @@ +/* KLDIFF.TXT - Differences between KLH10 and KL10 +*/ +/* $Id: kldiff.txt,v 2.3 2001/11/10 21:24:21 klh Exp $ +*/ +/* Copyright © 1994-1999, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +*/ + +This file attempts to summarize the known visible differences between +the KLH10 emulator's "KN10" processor and an actual DEC KL10, +specifically a Model B CPU running version 442 of the microcode. + +It is important to remember that it is very difficult to stumble +across these differences unless you are looking hard. As a practical +matter, for users there is far less of a difference between the KN10 +and a KL10 than there were between different releases of the KL10 +microcode. + +As one illustration of this point, DEC diagnostics are mentioned in +several places, but their reports must be taken with large grains of +salt. Many stopped working for the latest KL10s, and some only worked +when loaded with older microcode; in order to run those, the KLH10 must +be built with a "hybrid" KN10. + + +USER MODE DIFFERENCES +===================== + +[U1] No Public mode. + + The KN10 does not implement public mode; it was considered to +have little real use (the KS10 does not implement it), and would make +execution slower. This is discussed in more detail in the exec mode +section. + + +[U2] Floating point results with normalized operands. + + Some floating-point results may differ by a single low-order +bit, particularly FDV with negative operands. + This is noticed by the diagnostic DFKCA. + + +[U3] Floating point results with unnormalized operands. + + There are certain cases where unnormalized operands may +produce different results in the low-order bits, apparently due to +sloppiness in the KL10 hardware. + DFDV is the most notable example; when given wildly +unnormalized operands, some low-order bits are cleared by the KN10 to +produce logically correct results, but these same bits are filled with +mysterious garbage on the KL10. Note that the same operands on a KS10 +produce "clean" results that exactly match those of the KN10! + Diagnostics don't notice this as they never use unnormalized +operands. + + +[U4] Extended instruction set (string ops) + + These instructions have so many undefined aspects, and their +microcode implementation changed so often, that it is difficult to say +much here beyond acknowledging that certain esoteric failure cases may +well produce different results. "Normal" cases should all be +identical. + The diagnostic DFKCC detects no differences except for the +intermediate AC values of an interrupted CVTBDx, where a KL10 saves a +fractional quantity and the KN10 saves an integral quantity; but +nothing should actually be examining this intermediate value. + + +[U5] Traps (1,2,3), PC, Page Fault. + + Traps on the KN10 are handled in subtly different ways than +traps on a KL10. For the most part there is no observable difference, +but peculiar cases do exist. + In general, the PC seen by a trap instruction may be different +from that on a KL10 if the offending instruction (which set the trap +flags) was a jump or skip. However, either of these are problematical +for a KL10 trap handler as well. + On the KN10, an instruction that sets any trap flags will +trap as soon as the instruction is complete, with the PC pointing to +the next instruction (and backed up before executing the trap +instruction). + This differs from a KL10 where it is possible for the +next-instruction c(PC) fetch to cause a page fault before the trap is +handled. The diagnostic DFKEA notices this. N.B.: This is not the +same as a page fault during execution of either the offending +instruction or the trap instruction, both of which are correctly +handled. + +EXEC MODE DIFFERENCES +===================== + +Most of these are hidden from the user altogether. + + +[E1] No Public or Supervisor modes. + + The KN10 does not implement public mode; it was considered to +have little real use, and would make execution slower. The KN10 does +keep the Public bit in page map entries, but never actually sets +Public when fetching instructions from a Public page, nor does it +verify whether an operation is permitted if in Public/Supervisor mode. + Note that the DEC KS10 does not implement this either, so the +functionality is presumably not terribly important. + However, this confuses and ultimately crashes the diagnostic +DFKEB which tests specifically for the operational differences between +user, public, kernel, and supervisor modes. + + +[E2] No Previous-Context-Public PC flag. + + The KN10 always treats PC flag bit 0 as being "overflow", +which means that anything that stores the PC flags in exec mode will +get the state of the overflow flag, rather than the PCP flag as on a +real KL10. + This mildly irritates some diagnostics when run in exec mode, +specifically DFKCA, DFKCB, and DFKDA, which expect bit 0 to stay clear. + + +[E4] Page fail in interrupt instruction (IO Page Fail) + + The KN10 implements the documented behavior for this, which +is to trigger an IO-Page-Fail interrupt rather than taking a page fail +trap. However, according to the diagnostic DFKEA, a real KL10 should +take the trap, contrary to the Processor Reference Manual (p.3-65). +It is unclear whether DFKEA's interpretation is correct or corresponds +to old versions of the KL10. In any case, a monitor should never let +this happen in the first place. + + +[E5] PXCT in non-zero section + + The problem is that no one is sure any more exactly how PXCT +is *supposed* to work, or even how it *does* work on the KL10! The +original KN10 implementation conformed to the available +documentation, and passed the PXCT diagnostic DFKEC perfectly, but +caused page faults when used with real monitors and certain user +programs. The current modified implementation works for all TOPS-10 +and TOPS-20 software tested to date, but now triggers two failure +cases in DFKEC. + + +CPU HARDWARE DIFFERENCES +======================== + +There are several things about the KL10 hardware that are not +implemented at all in the KN10, either because the features are +never used by a monitor or would be too impractical to implement. +Without going into unnecessary detail, here are the main items. + +[C1] Interrupt Instructions - no BLKI, BLKO + Only JSR and XPCW are supported. + (plus AOSE, SKIPE, and JSP to keep diagnostics happy). + +[C2] Interrupt Functions - only those needed are implemented. + +[C3] CONI/CONO PI, - bits <18:20> not implemented. + The diagnostic DFKDA notices this. + +[C4] No Cache + All of the "Sweep Cache" instructions are no-ops that merely + clear Sweep Busy and set Sweep Done. The cache strategy bits <18:19> + in a CONI/CONO PAG, are ignored. + +[C5] No Address Break + DATAO APR, remembers the break information for a DATAI APR, + but the actual functionality is not implemented, and will probably + never be, for efficiency reasons. + The diagnostic DFKDA notices this. + +[C6] Time Base doubleword - not updated except by RDTIME. + +[C7] Interval Counter - not as precise. + +[C8] No Execution Account counter + +[C9] No Memory Account counter + +[C10] No Performance Analysis counter + +[C11] SBDIAG partially implemented + Only the SBDIAG functions necessary to keep the TOPS-10 and TOPS-20 + monitors happy are implemented. The emulated machine pretends to + have a MF20 that responds to the entire range of 22-bit physical + memory (4096K). diff --git a/doc/klt10.txt b/doc/klt10.txt new file mode 100644 index 0000000..1e54d04 --- /dev/null +++ b/doc/klt10.txt @@ -0,0 +1,93 @@ +/* KLT10.TXT - KL TOPS-10 system startup on KLH10 +*/ +/* $Id: klt10.txt,v 2.3 2001/11/10 21:24:21 klh Exp $ +*/ +/* Copyright © 1997, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +*/ + + [NOTE: Eventually this will describe how to install a TOPS-10 system + from an installation tape. For the time being it merely describes + startup using a ready-made filesystem that I may someday package + for distribution. Four monitors are included: + + 704NON.EXE - TOPS-10 V7.04 no network, won't try to load KLNI. + 704DCN.EXE - TOPS-10 V7.04 with DECnet and LAT + 704LAT.EXE - TOPS-10 V7.04 with just LAT (untested) + SYSTEM.EXE - TOPS-10 V7.04 for the KS. + + You will need to specify an appropriate monitor from this list when + prompted by BOOT>.] + + In the transcript that follows, all notes are prefaced by +triple semicolons (;;;), and user input is presented in lowercase +where possible -- note that the monitor sometimes echoes typein in +uppercase. Where there might be some confusion, a note such as "Type +CR" will identify what you need to type. + + ----------------------------------------- + + ;;; Here we go... + ;;; +% ./kn10-kl klt10-img.ini ;;; Invoke KLH10 + [usual output banners] +KLH10> ; KLH10 configuration for TOPS-10 test system +KLH10> +KLH10> ; DTE requires ackdly to avoid T10 race condition +KLH10> devdef dte0 200 dte master ackdly=5 +KLH10> devdef rh0 540 rh20 +KLH10> devdef rh1 544 rh20 +KLH10> devdef dsk0 rh0.0 rp type=rp06 sn=4747 format=raw path=T10.RP06.0 +KLH10> devdef dsk1 rh0.1 rp type=rp06 sn=1026 format=raw path=T10.RP06.1 +KLH10> devdef mta0 rh1.0 tm02 type=TU77 fmtr=TM03 +KLH10> +KLH10> ; NI: new param "c3dly" to see if it helps T10. +KLH10> ; NI: new param "rdtmo" to avoid system hangups with OSF/1 V3.0 +KLH10> ; +KLH10> ; Decnet node TWONKY, 9.429 +KLH10> devdef ni0 564 ni20 dedic=0 decnet=1 doarp=0 enaddr=aa:00:04:00:ad:25 dpdelay=12 c3dly=2 rdtmo=3 +KLH10> +KLH10> load klboot.exe +Using word format "c36"... +Loaded "klboot.exe": +Format: DEC-PEXE +Data: 0, Symwds: 0, Low: 01000000, High: 00, Startaddress: 0703667 Entvec: 00 wds at 00 +KLH10> [EOF on klt10-img.ini] +KLH10> go ;;; Type "go" to start KN10! +Starting KN10 at loc 0703667... +BOOT V4(100) + +BOOT>704non ;;; Enter desired monitor, then CR +[Loading from DSKA:704NON.EXE[1,4]] + +TOPS-10 704 No Network 11-Nov-93 +Why reload: sa ;;; "sa" or whatever +Date: ;;; Just enter CR +Time: ;;; Just enter CR +Startup option: no ;;; Example; could say "go". +[Rebuilding the system search list from the HOM blocks] + +[Rebuilding the active swapping list from the HOM blocks] + +[Rebuilding the system dump list from the HOM blocks] + + +TOPS-10 704 No Network Thursday 24-Apr-97 3:00:25 + +.log 1,2/by ;;; Log in like this to bypass accting +Job 1 TOPS-10 704 No Network CTY +03:07 24-Apr-97 Thursday + +. ;;; Ready to futz around... + ;;; ... do whatever, then when OK to bring down: +.[HALTED: FE interrupt] ;;; Type ^\ (CTRL-\) to return to KLH10 +KLH10> q ;;; OK to quit KLH10 now! TOPS-10 not fussy. +Are you sure you want to quit? [Confirm] +Shutting down...Bye! +% ;;; Back at unix shell prompt + diff --git a/doc/klt20.txt b/doc/klt20.txt new file mode 100644 index 0000000..da6bc3e --- /dev/null +++ b/doc/klt20.txt @@ -0,0 +1,628 @@ +/* KLT20.TXT - KL TOPS-20 system installation on KLH10 +*/ +/* $Id: klt20.txt,v 2.5 2001/11/19 12:14:54 klh Exp $ +*/ +/* Copyright © 1997, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +*/ + + Once you have installed the KLH10 binaries, you will need to +install a PDP-10 operating system on the KN10's virtual disks. This +file documents the basic procedure for TOPS-20 on a KL10B. + +The TOPS-20 installation procedures are described in a Digital +document titled "TOPS-20 KL Model B Installation Guide", available +online as a file called "install.mem" from some sites. Some of the +steps on a KN10 (aside from the obvious lack of hardware) may be +different, however. + +You will need an "installation" tape or tape image to get started, and +almost certainly will want to install the accompanying sources and +tools. You should already have a valid TOPS-20 license (personal or +otherwise) in order to use them. + +These are the tapes containing the last known DEC TOPS-20 distribution +for the KL10B (DEC-2065), which was TOPS-20 V7.0: + + BB-H137F-BM TOPS-20 V7.0 INSTL 16MT9 + BB-EV83B-BM TCP/IP-20 V4.0 DISTR 16MT9 + BB-H138F-BM T20 V7.0 DIST 16MT9 TAPE 1OF2 + BB-LW55A-BM T20 V7.0 DIST 16MT9 TAPE 2OF2 + BB-M780D-SM TOPS-20 V7.0 MONITOR SRC 16MT + BB-GS97B-SM TOPS-20 EXEC SRC 16MT9 + BB-M080Z-SM T20 V7.0 #04 MON SRC MOD 16MT + BB-M081Z-SM T20 V7.0 #04 EXEC SRC MOD 16M + BB-PENEA-BM TOPS-20 V7.0 TSU04 TP 1 OF 2 + BB-KL11M-BM TOPS-20 V7.0 TSU04 TP 2 OF 2 + BB-M836D-BM TOPS-20 V7.0 TOOLS 16MT9 + +It should be possible to obtain these from www.trailing-edge.com. In +particular, start with the first of these, which is the installation +tape: + ftp://ftp.trailing-edge.com/pub/pdp10/bb-h137f-bm.tap.gz + +The script that follows assumes you have uncompressed this into +"bb-h137f-bm.tap" in your $KLH10_HOME. + +NOTE: the installation tape does not contain a TCP/IP monitor +image. Before you can use the network, you will need to also restore +the files from the tape "BB-EV83B-BM TCP/IP-20 V4.0 DISTR 16MT9" +(there was more than one version; only the latest has monitor +images). + +In the transcript that follows, all notes are prefaced by triple +semicolons (;;;), and user input is presented in lowercase where +possible -- note that the monitor sometimes echoes typein in +uppercase. Where there might be some confusion, a note such as "Type +CR" will identify what you need to type. + + ===================================================== + +QUICK SUMMARY: + +$ ./kn10-kl inst-klt20.ini ;;; Configure and load tape bootstrap +KLH10> go +MTBOOT>/l ;;; Load rest of monitor +MTBOOT>/g143 ;;; Start at FS-creation entry point + ;;; After quiets down, ^C to get MEXEC, and carry on + + ;;; After installation finished: +$ ./kn10-kl klt20.ini ;;; Configure and load disk bootstrap +KLH10> go +BOOT> ;;; Type CR to load and start MONITR.EXE + + ===================================================== + +FULL SCRIPT: + + ;;; Here we go... + ;;; +$ ./kn10-kl inst-klt20.ini ;;; You type this +KLH10 V2.0 beta (MyKL) built Nov 10 2001 02:14:29 + Copyright © 2001 Kenneth L. Harrenstien -- All Rights Reserved. +This program comes "AS IS" with ABSOLUTELY NO WARRANTY. + +Compiled for LINUX on I386 with word model USEHWD +Emulated config: + CPU: KL10-extend SYS: T20 Pager: KL APRID: 1 + Memory: 8192 pages of 512 words (SHARED) + Time interval: INTRP Base: OSGET + Interval default: 60Hz + Internal clock: OSINT + Other: MCA25 JPC DEBUG PCCACHE CTYINT EVHINT + Devices: DTE RH20 RPXX(DP) TM03(DP) NI20(DP) +[MEM: Allocating 8192 pages shared memory, clearing...done] + +KLH10> ; Sample KLH10.INI for initial installation +KLH10> +KLH10> ; Define basic device config - one DTE, one disk, one tape. +KLH10> ; Use two RH20s because TOPS-10 doesn't like mixing disk and tape on +KLH10> ; the same controller (TOPS-20 is fine). +KLH10> +KLH10> devdef dte0 200 dte master +KLH10> devdef rh0 540 rh20 +KLH10> devdef rh1 544 rh20 +KLH10> devdef dsk0 rh0.0 rp type=rp06 format=dbd9 +[Creating RP06 disk file "RH20.RP06.1"] +KLH10> devdef mta0 rh1.0 tm03 type=tu45 +KLH10> +KLH10> ; Need KLNI to avoid LAPRBF BUGCHKs - use valid address if known +KLH10> ; +KLH10> devdef ni0 564 ni20 ipaddr=10.0.0.51 +KLH10> +KLH10> ; Mount installation tape (no ucode or boot to skip) +KLH10> devmount mta0 bb-h137f-bm.tap +Mount requested: "bb-h137f-bm.tap" +KLH10> +KLH10> ; Load tape bootstrap directly +KLH10> load mtboot.sav +Using word format "c36"... +Loaded "mtboot.sav": +Format: DEC-CSAV +Data: 4067, Symwds: 0, Low: 040000, High: 054641, Startaddress: 040000 +Entvec: JRST (120 ST: 0, 124 RE: 0, 137 VR: 0,,0) +KLH10> +KLH10> ; Now ready to GO +KLH10> [EOF on inst-klt20.ini] +KLH10> +[mta0: Tape online] ;;; Init file now done. +KLH10> go ;;; Boot is loaded, type "go" to start KN10! +Starting KN10 at loc 040000... + +BOOT V11.0(315) + +MTBOOT>/l ;;; Type "/l" to load monitor + +[BOOT: Loading] [OK] + +MTBOOT>/g143 ;;; Now type "/g143" to start monitor + ;;; at the filesystem-create entry address. + ;;; Answer the following questions as shown, + ;;; unless you know what you're doing. + +[FOR ADDITIONAL INFORMATION TYPE "?" TO ANY OF THE FOLLOWING QUESTIONS.] + +DO YOU WANT TO REPLACE THE FILE SYSTEM ON THE SYSTEM STRUCTURE? Y ;;; + +DO YOU WANT TO DEFINE THE SYSTEM STRUCTURE? Y ;;; + +HOW MANY PACKS ARE IN THIS STRUCTURE: 1 ;;; + +ON WHICH "CHANNEL,CONTROLLER,UNIT" IS LOGICAL PACK # 0 MOUNTED? 0,-1,0 ;;; + +DO YOU WANT THE DEFAULT SWAPPING SPACE? Y ;;; + +DO YOU WANT THE DEFAULT SIZE FRONT END FILE SYSTEM? Y ;;; + +DO YOU WANT THE DEFAULT SIZE BOOTSTRAP AREA? Y ;;; + +DO YOU WANT TO ENABLE PASSWORD ENCRYPTION FOR THE SYSTEM STRUCTURE? Y ;;; + +WHAT IS THE NAME OF THIS STRUCTURE? PS ;;; + +[STRUCTURE "PS" SUCCESSFULLY DEFINED] + +[PS MOUNTED] +?PS UNIT 0 HAS NO BAT BLOCKS. +DO YOU WANT TO WRITE A SET OF PROTOTYPE BAT BLOCKS? Y + +%%NO SETSPD. + +System restarting, wait... +DATE AND TIME IS: SATURDAY, 29-SEPTEMBER-2001 10:40PM +WHY RELOAD? NEW ;;; Type "new" or whatever. +PROBLEM WITH ACCOUNTS-TABLE.BIN +CANNOT FIND ERROR MESSAGE FILE - ACCOUNT VALIDATION IS DISABLED + +RUNNING DDMP + + +NO SYSJOB + ;;; Unless you have fixed up the NI20 definition + ;;; in the klh10.ini file, you will probably see + ;;; a few KNI BUGCHKs which are harmless at this + ;;; point, such as the following: +******************** +*BUGCHK "KNICFF" AT 29-SEP-2001 22:40:28 +*PHYKNI - CANNOT RELOAD THE KLNI +*JOB: 0, USER: OPERATOR +*ADDITIONAL DATA: 600104 +******************** + ;;; After you get "NO SYSJOB" you can type either + ;;; a CR or ^C to get to the MEXEC prompt ("MX>"). +NO EXEC +MX>GET FILE MTA0: ;;; Type just "g" then "mta0:" and CR. + ? ;;; Monitor will read an EOF and do nothing. +MX>GET FILE MTA0: ;;; Repeat, to read EXEC.EXE from tape. +MX>START ;;; Type just "s" then CR. + + TOPS-20 Command processor 7(4143) +@TER NO RAI ;;; Success! Let's stop uppercasing our input, +@ena ;;; and must get enabled. +$run mta0: ;;; Now read and start DLUSER.EXE from tape. +DLUSER>load mta0: ;;; Now read DLUSER data to set up directories. + +DONE. +DLUSER>exit ;;; Get out. +$run mta0: ;;; Now read and start DUMPER.EXE from tape. +DUMPER>tape mta0: ;;; Point to tape drive and start restoring files! + ;;; Note in the next command it is important to + ;;; explicitly specify "", otherwise the + ;;; files will be restored into "". +DUMPER>restore <*>*.*.* (TO) *.*.* +Saveset "SYSTEM Files for TOPS-20 V7.0" 22-Jun-88 2035 + Loading files into PS: + End of Saveset. + + + Total files restored: 26 + Total pages restored: 1765 + ;;; Note in the next command it is important to + ;;; explicitly specify "", otherwise the + ;;; files will be restored into "". +DUMPER>restore <*>*.*.* (TO) *.*.* +Saveset "SUBSYS Files for TOPS-20 V7.0" 22-Jun-88 2037 + Loading files into PS: + End of Saveset. + + + Total files restored: 176 + Total pages restored: 4535 + ;;; Note in the next command it is important to + ;;; explicitly specify "", otherwise it + ;;; will try and fail to restore the files into + ;;; "". +DUMPER>restore <*>*.*.* (TO) *.*.* +Saveset "GALAXY SUBSYS Files for TOPS-20 V7.0" 22-Jun-88 2043 + Loading files into PS: + End of Saveset. + + + Total files restored: 25 + Total pages restored: 586 + ;;; The last saveset is optional and can be skipped. + ;;; If you want the UETP stuff, do: +DUMPER> +DUMPER>restore <*>*.*.* (TO) <*>*.*.* + Loading files into PS: + End of Saveset. + + + Total files restored: 54 + Total pages restored: 477 + +DUMPER>exit ;;; Done, with or without UETP. +$conn +$vdir ;;; Check out what we have in + + PS: + 2060-MONBIG.EXE.1;P777752 597 305664(36) 26-May-88 17:05:33 BROOKS + 2060-MONMAX.EXE.1;P777752 597 305664(36) 26-May-88 17:28:27 BROOKS + 7-PTYCON.ATO.1;P777752 1 823(7) 2-Jun-88 22:29:16 BROOKS + 7-SETSPD.EXE.1;P777752 14 7168(36) 28-May-88 02:21:05 BROOKS + 7-SYSJOB.EXE.1;P777752 7 3584(36) 28-May-88 03:11:07 BROOKS + .RUN.1;P777752 1 158(7) 2-Jun-88 21:41:35 BROOKS + BOOT.EXB.1;P777752 12 11658(18) 27-May-88 21:36:22 BROOKS + BUGS.MAC.1;P777752 155 395399(7) 23-May-88 18:14:48 BROOKS + BUGSTRINGS.TXT.1;P777752 22 54680(7) 26-May-88 17:28:13 BROOKS + CHECKD.EXE.1;P777752 22 11264(36) 27-May-88 21:42:19 BROOKS + ERRMES.BIN.1;P777752 24 12013(36) 28-May-88 01:48:31 BROOKS + EXEC.EXE.1;P777752 125 64000(36) 27-May-88 11:40:08 BROOKS + FEDDT.EXE.1;P777752 9 4608(36) 14-Feb-79 20:37:17 BROOKS + IPALOD.EXE.1;P777752 28 14336(36) 28-May-88 00:53:56 BROOKS + KNILDR.EXE.1;P777752 26 13312(36) 28-May-88 00:58:59 BROOKS + MTBOOT.EXB.1;P777752 11 10247(18) 27-May-88 21:37:31 BROOKS + PROGRAM-NAME-CACHE.TXT.1;P777752 1 78(7) 9-Mar-81 15:24:00 BROOKS + RP2DBT.EXB.1;P777752 18 17687(18) 27-May-88 21:38:15 BROOKS + RP2MBT.EXB.1;P777752 16 16276(18) 27-May-88 21:39:26 BROOKS + RSX20F.MAP.1;P777752 29 74007(7) 22-Mar-88 14:17:44 BROOKS + SYSJOB.HLP.1;P777752 3 5679(7) 29-Mar-82 15:47:02 BROOKS + SYSTEM.CMD.1;P777752 1 598(7) 24-Feb-84 20:01:23 BROOKS + TGHA.EXE.1;P777752 29 14848(36) 28-May-88 03:20:32 BROOKS + .HLP.1;P777752 1 214(7) 11-Nov-86 20:49:44 BROOKS + TOPS20.BWR.1;P777752 3 1425(36) 1-Jun-88 18:17:20 BROOKS + .DOC.1;P777752 13 6432(36) 21-Jun-88 15:25:11 LOMARTIRE + + Total of 1765 pages in 26 files +$ +$ ;;; For convenience, now is a good time to + ;;; make MONITR.EXE be your desired monitor! +$renAME (EXISTING FILE) 2060-monbig.EXE.* (TO BE) monitr.exe + 2060-MONBIG.EXE.1 => MONITR.EXE.1 [OK] +$^Ecease now ;;; OK, now we can try a disk boot. + TOPS20 Will be shut down IMMEDIATELY +[Confirm] +$ +[Timesharing is over] + + OPERATOR - Wait for the message "Shutdown complete" before + entering commands to PARSER. + +Shutdown complete ;;; When you see this, type ^\ (CTRL-\) +[HALTED: FE interrupt] ;;; to return to KLH10 +KLH10> shutdown ;;; Then give "shutdown" command. +Continuing KN10 at loc 01047523... +**HALTED** +[HALTED: Program Halt, PC = 1050257] +KLH10> q ;;; OK to quit KLH10 now! +Are you sure you want to quit? [Confirm] +Shutting down...Bye! +$ ;;; Back at unix shell prompt +$ +$ ./kn10-kl klt20.ini ;;; Fire it up again, with different ini file +KLH10 V2.0 beta (MyKL) built Nov 10 2001 02:14:29 + Copyright © 2001 Kenneth L. Harrenstien -- All Rights Reserved. +This program comes "AS IS" with ABSOLUTELY NO WARRANTY. + +Compiled for LINUX on I386 with word model USEHWD +Emulated config: + CPU: KL10-extend SYS: T20 Pager: KL APRID: 1 + Memory: 8192 pages of 512 words (SHARED) + Time interval: INTRP Base: OSGET + Interval default: 60Hz + Internal clock: OSINT + Other: MCA25 JPC DEBUG PCCACHE CTYINT EVHINT + Devices: DTE RH20 RPXX(DP) TM03(DP) NI20(DP) +[MEM: Allocating 8192 pages shared memory, clearing...done] + +KLH10> ; Sample KLH10.INI for initial installation +KLH10> +KLH10> ; Define basic device config - one DTE, one disk, one tape. +KLH10> ; Use two RH20s because TOPS-10 doesn't like mixing disk and tape on +KLH10> ; the same controller (TOPS-20 is fine). +KLH10> +KLH10> devdef dte0 200 dte master +KLH10> devdef rh0 540 rh20 +KLH10> devdef rh1 544 rh20 +KLH10> devdef dsk0 rh0.0 rp type=rp06 format=dbd9 +KLH10> devdef mta0 rh1.0 tm03 type=tu45 +KLH10> +KLH10> ; Need KLNI to avoid LAPRBF BUGCHKs - use valid address if known +KLH10> ; +KLH10> devdef ni0 564 ni20 ipaddr=10.0.0.51 +KLH10> +KLH10> ; Load disk bootstrap directly +KLH10> load boot.sav +Using word format "c36"... +Loaded "boot.sav": +Format: DEC-CSAV +Data: 4630, Symwds: 0, Low: 040000, High: 054641, Startaddress: 040000 +Entvec: JRST (120 ST: 0, 124 RE: 0, 137 VR: 0,,0) +KLH10> +KLH10> ; Now ready to GO +KLH10> [EOF on klt20.ini] +KLH10> go +Starting KN10 at loc 040000... + +BOOT V11.0(315) + +BOOT> ;;; Type just CR, will default to MONITR.EXE + +[BOOT: Loading] [OK] + + +[PS MOUNTED] + +System restarting, wait... +DATE AND TIME IS: SATURDAY, 29-SEPTEMBER-2001 10:46PM +WHY RELOAD? NEW ;;; Or whatever reason you want +PROBLEM WITH ACCOUNTS-TABLE.BIN +CANNOT GET A JFN FOR ACCOUNTS-TABLE.BIN - ACCOUNT VALIDATION IS +DISABLED +RUN CHECKD? N ;;; Type "n". Shouldn't be necessary yet. + +RUNNING DDMP + + +SYSJOB 7(78) STARTED AT 29-SEP-2001 2246 +RUN SYS:INFO +RUN SYS:MAPPER +JOB 0 /LOG OPERATOR XX OPERATOR +ENA +^ESET LOGIN PSEUDO +^ESET LOGIN CONSOLE +^ESET OPERATOR +PTYCON +GET SYSTEM:7-PTYCON.ATO +/ +SJ 0: @LOG OPERATOR OPERATOR +SJ 0: JOB 1 ON TTY172 29-SEP-2001 22:46:51, LAST LOGIN NEVER +SJ 0: +[dpni20: Using default interface "eth0"] +[KNILDR: LOADING MICROCODE VERSION 1(172) INTO ETHERNET CHANNEL 0 +] + @ENA +SJ 0: $^ESET LOGIN PSEUDO +SJ 0: $^ESET LOGIN CONSOLE +SJ 0: $^ESET OPERATOR +SJ 0: $PTYCON +SJ 0: PTYCON> GET SYSTEM:7-PTYCON.ATO +SJ 0: PTYCON> SILENCE +SJ 0: PTYCON> W ALL +SJ 0: GAL(0) 2 OPERATOR EXEC TI 0:0:0 +SJ 0: BAT(1) 3 OPERATOR EXEC TI 0:0:0 +SJ 0: NEB(2) 4 OPERATOR EXEC TI 0:0:0 +SJ 0: OPR(3) 5 OPERATOR EXEC TI 0:0:0 +SJ 0: MAILS(4) 6 OPERATOR MX RN 0:0:0 +SJ 0: PTYCON> CONN OPR +SJ 0: [CONNECTED TO SUBJOB OPR(3)] + ;;; When things seem quiet, type CR to get an EXEC. + + TOPS-20 Big System, TOPS-20 Monitor 7(21017) +@TER NO RAI +@log operator ;;; Try logging in, pwd "DEC-20" + Job 7 on TTY145 29-Sep-2001 22:47:24, Last Login 29-Sep-2001 22:46:57 +@ena +$ ;;; Now do whatever you wish... + + ===================================================== + +Other things you probably want to do at some point: + + +$copy tty: monnam.txt ;;; Set the system name +MyKL TOPS-20 System +^Z +$copy tty: 4-1-config.cmd ;;; Set timezone here for now +timezone 8 +^Z +$^Ecreate ;;; Change operator password +$$password tqbfjotld ;;; (use something better) +$$ +$^Ecreate ;;; Create your account +$$password nittfagmtc2taotp ;;; (or something better) +$$wheel +$$working 10000 +$$permanent 10000 +$$ +$ + +There are large manuals devoted to TOPS-20 system maintenance, but +this is enough to make you dangerous. + +TOPS-20 TCP/IP INSTALLATION +=========================== + + This aspect is probably the one that many KLH10 users will be +most interested in. Unfortunately I ran out of time to provide a full +script, but this should be enough to get you going: + +(1) Install all of the V7 install tape (as above) + +(2) Restore all files from the following tape: + + BB-EV83B-BM TCP/IP-20 V4.0 DISTR 16MT9 + + This one is a pain, because the DLUSER.DAT on the install tape doesn't + set up the necessary directories beforehand, so you will have to create + them all by hand (make sure they are files-only!). + +(3) Assign a valid IP address for your system, if you don't already have + one. This should be something consistent with your physical subnet. + +(4) Create the file INTERNET.ADDRESS with either an editor or + a COPY from the TTY:, in the following format (replacing the + "192 168 0 201" with your desired IP address): + +$vd internET.*.* + + PS: + INTERNET.ADDRESS.1;P777752 1 57(7) 5-Oct-2001 22:06:55 OPERATOR +$ty internET.ADDRESS.1 +IPNI#0,192 168 0 201,PACKET-SIZE:1500,DEFAULT,PREFERRED +$ + Keep the packet size at 1500. I found it set to 1504 once and + that was breaking things. + + +(4) Now reboot, with this devdefine added to your KLH10 config file: + + devdef ni0 564 ni20 ipaddr=192.168.0.201 + + (Again, use your correct IP address here) + + +(5) Manually specify this to the BOOT> prompt: + + AN-MONMAX + + Or you can just copy or rename that file to MONITR.EXE to + make it the default. + +(6) After system comes up, telnet in from ANOTHER machine. Depending on + your platform it's unlikely you will be able to telnet in from the + same host that the emulator is running on, unless you are using + a separate dedicated interface. + + If this succeeds, take a break and have a . + +(7) About now you will start to get really annoyed by periodic bleats that + look like this: + +SJ 0: [SCHEDULER]: Waiting for UPS: +SJ 0: **** OPR(3) 22:18:48 **** +SJ 0: **** MAILS(4) 22:19:48 **** + + This can be permanently turned off by removing its invocation from + the 7-PTYCON.ATO startup file, but a good temporary fix is simply + to kill the offending job. + + Do a SYS to find the job called MX, then forcibly log it out: + +$sys + Fri 5-Oct-2001 23:05:45 Up 0:53:26 + 0+8 Jobs Load av 0.03 0.03 0.03 + + Job Line Program User Origin + 1 206 PTYCON OPERATOR + 2 207 EXEC OPERATOR + 3 210 EXEC OPERATOR + 4 211 EXEC OPERATOR + 5 212 EXEC OPERATOR + 6 213 MX OPERATOR + 7* 205 SYSTAT OPERATOR + 8 270 EXEC OPERATOR 192.168.0.33(TCP) +$ +$logo 6 +User OPERATOR on TTY213, running MX +[Confirm] +$ + +TOPS-20 TCP/IP - OTHER NOTES +============================ + +These are miscellaneous notes from my files. I have not verified +whether they apply to the vanilla DEC TCP/IP system. + + +SYSTEM:KNILDR.EXE -- utility that can be used to restart the NI20. + The useful commands are "HALT 0" and "START 0" which will stop and + start the KLNI in case something went wrong during startup (timing + races) or the KLH10 DPNI20 subprocess needs to be replaced with a new + version without bringing the system down. Run this from the console, + or you'll saw off the branch you're sitting on. + + +---- SYSTEM:INTERNET.ADDRESS ----- +---- Read on system startup; no provision I can see for re-reading any + other time. + + ;;;Internet addresses for system + IPNI#0, 16 151 16 13,DEFAULT,PREFERRED,PACKET-SIZE:1504 + +---- SYSTEM:INTERNET.GATEWAYS ----- +---- Read on system startup, also with "^Einit gateways" + + ; IP addresses of local prime gateway; if only one, not clear if + ; its args matter, but provide something plausible. + PRIME 192 33 33 3, 128 18 1 1 + +---- SYSTEM:HOSTS.TXT ----- +---- Read on system startup, also with "^Einit hosts" + + ; Arbitrarily long, but basically defines local nets and hosts + ; we need to know about in absence of DNS (CHIVES) info. + + +---- SYSTEM:HOSTNAME.TXT ----- +---- Can't find anything in the monitor that reads this file, but this +---- should be similar to MONNAM.TXT except for containing the FULL domain +---- name of the system. E.g.: + + BOOTSTRAP.SRI.COM + +---- SYSTEM:MONNAM.TXT ----- +---- Now *this* definitely is read on system startup. + + BOOTSTRAP + + +---- SYSTEM:INTERNET-ETHERNET-MAPPINGS.BIN +---- Read on system startup; can also be reloaded by running IPHOST + with a command (ETHER INIT?) + This is apparently generated by the IPHOST program using the + command BUILD, which takes SYSTEM:INTERNET-ETHERNET-MAPPINGS.TXT + and translates it into .BIN. + TXT file format *appears* to be: + HOST xx-xx-xx-xx-xx-xx ddd.ddd.ddd.ddd [/ARP] [/NOARP] + BIN file format is documented in ANAUNV.MAC. + + Current file is basically empty, contains one zero entry; + apparently this just makes everything use ARP. + + +---- SYSTEM:NETSRV.RUN ----- +---- File of NETSRV commands, executed by NETSRV.EXE (started by SYSJOB) + This basically determines what TCP/IP servers will be started. + Note: NETSRV is not a DEC program; it is one of many non-DEC utilities + produced by the ARPANET user community, in this case Mark Crispin + and Kevin Paetzold. + + +---- SYS:FTSCTT.EXE ----- +---- In a vanilla DEC setup this is the FTP server program. You can + put the line "RUN SYS:FTSCTT" in 7-SYSJOB.RUN to start it + up automatically. You may also want to set the protection of + FTPSRT.EXE from 777700 to 777752 to allow general use. + + +---- System:INTERNET-LOGIN-MESSAGE.TXT ----- +---- Contains initial login banner presented to users. May be a feature + of the SRI-NIC EXEC only. + + +General stuff, not network specific: + + +SYSTEM:7-CONFIG.CMD + File of commands for SYSTEM:SETSPD.EXE, which is executed by + monitor at startup (prior to running SYSJOB). + +SYSTEM:SYSJOB.RUN + File of commands for SYSTEM:SYSJOB.EXE, which is also run by + monitor at startup. diff --git a/doc/ksits.txt b/doc/ksits.txt new file mode 100644 index 0000000..f5a6a81 --- /dev/null +++ b/doc/ksits.txt @@ -0,0 +1,81 @@ +/* KSITS.TXT - KS ITS system installation on KLH10 +*/ +/* $Id: ksits.txt,v 2.4 2001/11/10 21:24:21 klh Exp $ +*/ +/* Copyright © 1999, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +*/ + +This is mainly a placeholder until distribution issues are resolved +for the starter ITS file system copy. + +The run/ksits directory contains the boot files developed for the +Public ITS project, which had 3 systems called PI, DU, DX. These are +for PI: + + klh10-pi.ini - KLH10 configuration for PI. + + @.its-647pi-u - ITS. Normally you would just load this. + @.ddt-u - DDT stand-alone. + @.nsalv-260-u - SALV stand-alone. Start at 200000 !! + itsbin.647pi-u - ITS binary, without DDT or SALV. + + Note: All binaries by default are in U36 format, as opposed + to the C36 format of all other KN10 configurations. + +Eventually I hope to be able to distribute the actual filesystem image +as well, and then will include a large bunch of stuff containing +nearly everything I've discovered or written about ITS software +porting issues, machine names and addresses, dumps, utilities, bug +fixes, etc. + +THE FIRST TIME +============== + +Once you have installed the binaries per install.txt in a $KLH10_HOME +corresponding to an instance of run/ksits (don't forget to make +dpimp setuid root, or run as root) it should work to just do: + + ./kn10-ks klh10-kn.ini ;;; or whichever system you prefer) + [gubbish] + KLH10> go + +After the dust settles, type ^Z. +The first time, ITS will complain that the time is not known. +Run PDSET. + +To correctly set the date in the new millenium, you must use the +undocumented command "C", as in "20C". + +------------------------------------------------- + +pdset^K(Please Log In) ;;; or :pdset +! +___002 PDSET IOTLSR + +PDSET.114 +Please don't use this program unless you know how. +You are certain to break something if you happen to hit the wrong key. +Type Control-Z to exit, or ? for a reminder of the commands. +20C ;;; You type "20C" +011110D ;;; You type YYMMDD and D. YY=01 for 2001. +131012T ;;; Ditto for time, HHMMSS and T. +!. ;;; Finish with "!." +141425/ 765361,,514221 763707,,355545 ___002 + +IT IS NOW 1:10:12 PM EST, SATURDAY, NOV 10,2001 +IT IS NOW 1:10:14 PM EST, SATURDAY, NOV 10,2001 +Q ;;; Get out with "Q" +:KILL +* +------------------------------------------------- + +From here on, ITS should always know the time. If it ever gets totally +out of whack, delete the file $KLH10_HOME/APR.TIMEBASE and reboot, +running PDSET again. + diff --git a/doc/kst10.txt b/doc/kst10.txt new file mode 100644 index 0000000..d23fca4 --- /dev/null +++ b/doc/kst10.txt @@ -0,0 +1,16 @@ +/* KST10.TXT - KS TOPS-10 system startup on KLH10 +*/ +/* $Id: kst10.txt,v 2.3 2001/11/10 21:24:21 klh Exp $ +*/ +/* Copyright © 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +*/ + + [NOTE: Eventually this will describe how to install a KS TOPS-10 system + from an installation tape. For the time being, see + klt10.txt instead.] diff --git a/doc/kst20.txt b/doc/kst20.txt new file mode 100644 index 0000000..eebbd4f --- /dev/null +++ b/doc/kst20.txt @@ -0,0 +1,587 @@ +/* KST20.TXT - KS TOPS-20 system installation on KLH10 +*/ +/* $Id: kst20.txt,v 2.3 2001/11/10 21:24:21 klh Exp $ +*/ +/* Copyright © 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +*/ + +This file contains several sections: + + KS TOPS-20 INSTALLATION (including tape and disk boot scripts) + KS T20 BOOT BUG NOTES + KS T20 MONITOR BUG + + +KS TOPS-20 INSTALLATION +======================= + + Once you have installed the KLH10 binaries, you will need to +install a PDP-10 operating system on the KN10's virtual disks. This +file documents the basic procedure for TOPS-20 on a KS10 (2020). + +Although the TOPS-20 installation procedures were certainly listed +somewhere in Digital's documentation, I'm not sure what references to +provide; they may be difficult or impossible to find. In any case, +some of the steps on a KN10 will be different. + +(NB: the field service documentation files "red20.hlp" and "red20.mem" +helped me, but they were not intended for customers.) + +You will need an "installation" tape or tape image to get started, and +almost certainly will want to install the accompanying sources and +tools. You should already have a valid TOPS-20 license (personal or +otherwise) in order to use them. + +These are the tapes containing the last known DEC TOPS-20 distribution +for the KS10 (DEC-2020), which was TOPS-20 V4.1: + + BB-D867E-BM TOPS-20 V4.1 2020 INSTL 16MT9 1983 + BB-D868E-BM T-20 V4.1 2020 DIST 16MT9 1/2 1983 + BB-V895A-BM T-20 V4.1 2020 DIST 16MT9 2/2 1983 ("1/9/85") + BB-J710B-BM TOPS-20 V4.1 TOOLS 16MT9 1983 ("3/27/85") + Tops-20 V4.1 TP#12 "Exec Src Mod" Gidney TU78 21-Nov-85 + Tops-20 V4.1 TP#12 "Monitor Sources" Gidney TU78 21-Nov-85 + +It should be possible to obtain these from www.trailing-edge.com. In +particular, start with the first of these, which is the installation +tape: + +ftp://ftp.trailing-edge.com/pub/pdp10/bb-d867e-bm_tops20_v41_2020_instl.tap.gz + +The script that follows assumes you have uncompressed this into the +somewhat more manageable name of "bb-d867e-bm.tap" in your $KLH10_HOME. + +In the transcript that follows, all notes are prefaced by triple +semicolons (;;;), and user input is presented in lowercase where +possible -- note that the monitor sometimes echoes typein in +uppercase. Where there might be some confusion, a note such as "Type +CR" will identify what you need to type. + + ===================================================== + +QUICK SUMMARY: + +$ ./kn10-ks inst-kst20.ini ;;; Configure and load tape bootstrap + ;;; (normally smmtbt-k.sav, entry 40000) +KLH10> go +MTBOOT>/l ;;; Load rest of monitor +MTBOOT>/g143 ;;; Start at FS-creation entry point + ;;; (/g147 if a filesystem already exists) + ;;; After quiets down, ^C to get MEXEC, and carry on + + ;;; After installation finished: +$ ./kn10-ks klh10.ini ;;; Configure and load disk bootstrap +KLH10> go +BOOT> ;;; Type CR to load and start MONITR.EXE + + ===================================================== + +FULL SCRIPT: + + ;;; Here we go... + ;;; +$ ./kn10-ks inst-kst20.ini ;;; You type this +KLH10 V2.0 beta (MyKS) built Nov 10 2001 02:14:29 + Copyright © 2001 Kenneth L. Harrenstien -- All Rights Reserved. +This program comes "AS IS" with ABSOLUTELY NO WARRANTY. + +Compiled for LINUX on I386 with word model USEHWD +Emulated config: + CPU: KS10 SYS: T20 Pager: KL APRID: 759 + Memory: 1024 pages of 512 words (SHARED) + Time interval: INTRP Base: OSGET + Interval default: 60Hz + Internal clock: OSINT + Other: JPC DEBUG PCCACHE CTYINT IMPINT EVHINT + Devices: RH11 RPXX(DP) TM03(DP) +[MEM: Allocating 1024 pages shared memory, clearing...done] + +KLH10> ; This is a sample KLH10 config file for a KS10 running TOPS-20. +KLH10> +KLH10> ; Define basic KS10 device config - two RH11s each on its own Unibus +KLH10> +KLH10> devdef rh0 ub1 rh11 addr=776700 br=6 vec=254 +KLH10> devdef rh1 ub3 rh11 addr=772440 br=6 vec=224 +KLH10> +KLH10> ; Provide one disk, one tape in config T20 expects +KLH10> +KLH10> devdef dsk0 rh0.0 rp type=rp06 format=dbd9 path=T20-RP06.0-dbd9 iodly=0 +KLH10> [dprpxx: WARNING! sigpend 37, 40, 43, 44, 45, 46, 47, 48, 49, 63] +[Creating RP06 disk file "T20-RP06.0-dbd9"] +devdef mta0 rh1.0 tm03 fmtr=tm03 type=tu45 +KLH10> +KLH10> ; Mount installation tape, skipping past ucode and defective boot +KLH10> devmount mta0 bb-d867e-bm.tap fskip=2 +Mount requested: "bb-d867e-bm.tap" +[dptm03: WARNING! sigpend 33, 35, 37, 39, 40, 42, 44, 46, 48, 50, 63] +KLH10> +[mta0: Tape online] +KLH10> ; Load fixed tape bootstrap directly +KLH10> load smmtbt-k.sav +Using word format "c36"... +Loaded "smmtbt-k.sav": +Format: DEC-CSAV +Data: 1017, Symwds: 0, Low: 040000, High: 044010, Startaddress: 040000 +Entvec: JRST (120 ST: 0, 124 RE: 0, 137 VR: 0,,0) +KLH10> +KLH10> ; Now ready to GO +KLH10> [EOF on inst-kst20.ini] ;;; Init file now done. +KLH10> go ;;; Boot is loaded, type "go" to start KN10! +Starting KN10 at loc 040000... + +MTBOOT>/l ;;; Type "/l" to load monitor + +MTBOOT>/g143 ;;; Now type "/g143" to start monitor +uba_read: Bad unibus IO addr: 760540 ;;; KN10 reports probes of non-ex +uba_read: Bad unibus IO addr: 760010 ;;; devices; these are harmless. + + +[FOR ADDITIONAL INFORMATION TYPE "?" TO ANY OF THE FOLLOWING QUESTIONS.] + +DO YOU WANT TO REPLACE THE FILE SYSTEM ON THE PUBLIC STRUCTURE? Y + +DO YOU WANT TO DEFINE THE PUBLIC STRUCTURE? Y + +HOW MANY PACKS ARE IN THIS STRUCTURE: 1 + +ON WHICH "CHANNEL,UNIT" IS LOGICAL PACK # 0 MOUNTED: 0,0 + +DO YOU WANT THE DEFAULT SWAPPING SPACE? Y + +DO YOU WANT THE DEFAULT SIZE FRONT END FILE SYSTEM? Y ;;; Unnec but OK + +DO YOU WANT THE DEFAULT SIZE BOOTSTRAP AREA? Y + +[STRUCTURE "PS" SUCCESSFULLY DEFINED] + +[PS MOUNTED] +?PS UNIT 0 HAS NO BAT BLOCKS. +DO YOU WANT TO WRITE A SET OF PROTOTYPE BAT BLOCKS? Y +uba_read: Bad unibus IO addr: 775400 +uba_read: Bad unibus IO addr: 777160 + +%%NO SETSPD + +System restarting, wait... +ENTER CURRENT DATE AND TIME: 30-SEP-2001 0605 ;;; Must enter time, sigh + +YOU HAVE ENTERED SUNDAY, 30-SEPTEMBER-2001 6:05AM, + IS THIS CORRECT (Y,N) Y +WHY RELOAD? NEW ;;; Type "new" or whatever. + +ACCOUNTS-TABLE.BIN NOT FOUND - ACCOUNT VALIDATION IS DISABLED +uba_read: Bad unibus IO addr: 760540 + +RUNNING DDMP + + +NO SYSJOB + ;;; After you get "NO SYSJOB" you can type either + ;;; a ^C to get to the MEXEC prompt ("MX>"). +NO EXEC +MX>GET FILE MTA0: ;;; Type just "g" then "mta0:" and CR. + +INTERRUPT AT 0 ;;; Monitor will read an EOF and do nothing. +MX>GET FILE MTA0: ;;; Repeat, to read EXEC.EXE from tape. +MX>START ;;; Type just "s" then CR. + + TOPS-20 Command processor 5.1(1354) +@TER NO RAI ;;; Success! Let's stop uppercasing our input, +@ena ;;; and must get enabled. +$run mta0: ;;; Now read and start DLUSER.EXE from tape. +DLUSER>load mta0: ;;; Now read DLUSER data to set up directories. + +DONE. +DLUSER>exit ;;; Get out. +$run mta0: ;;; Now read and start DUMPER.EXE from tape. +DUMPER>tape mta0: ;;; Point to tape drive and start restoring files! + ;;; Note in the next command it is important to + ;;; explicitly specify "", otherwise the + ;;; files will be restored into "". +DUMPER>restore <*>*.*.* (TO) *.*.* + + +DUMPER tape # 1, "SYSTEM files for TOPS-20 V4.1", Thursday, 7-Apr-83 17 +21 +Loading file(s) into PS: + +End of saveset + ;;; Note in the next command it is important to + ;;; explicitly specify "", otherwise the + ;;; files will be restored into "". +DUMPER>restore <*>*.*.* (TO) *.*.* + + +DUMPER tape # 1, "SUBSYS files for TOPS-20 V4.1", Thursday, 7-Apr-83 17 +23 +Loading file(s) into PS: + +End of saveset + ;;; The last saveset is optional and can be skipped. + ;;; If you want the UETP stuff, do: +DUMPER>restore <*>*.*.* (TO) <*>*.*.* + + +DUMPER tape # 1, "UETP files for TOPS-20 V4.1", Thursday, 7-Apr-83 1726 +Loading file(s) into PS: + +End of saveset +DUMPER>exit ;;; Done, with or without UETP. +$conn +$vdir ;;; Check out what we have in + + PS: + 2020-ARPA-MONMED.EXE.1;P777700 321 164352(36) 6-Apr-83 00:28:29 PURRET +TA + 2020-ARPA-MONSML.EXE.1;P777700 321 164352(36) 6-Apr-83 00:24:37 PURRET +TA + 2020-MONMED.EXE.1;P777700 326 166912(36) 5-Apr-83 23:37:27 PURRETTA + 2020-MONSML.EXE.1;P777700 326 166912(36) 5-Apr-83 23:34:00 PURRETTA + 4-1-SETSPD.EXE.1;P777700 10 5120(36) 7-Dec-82 16:19:35 PURRETTA + BUGS.MAC.1;P777700 50 127461(7) 10-Nov-82 23:09:16 PURRETTA + BUGSTRINGS.TXT.1;P777700 10 24240(7) 6-Apr-83 00:28:24 PURRETTA + CHECKD.EXE.1;P777700 17 8704(36) 7-Dec-82 16:53:16 PURRETTA + DUMP.EXE.1;P777700 1025 524800(36) 13-Feb-80 17:44:06 PURRETTA + ERRMES.BIN.1;P777752 16 7717(36) 20-Dec-82 16:18:48 PURRETTA + ERROR.SYS.1;P777752 1 23(36) 30-Sep-2001 06:05:03 OPERATOR + EXEC.EXE.1;P777752 102 52224(36) 1-Dec-82 02:17:14 PURRETTA + FTPSER.EXE.1;P777700 17 8704(36) 15-Apr-82 10:14:40 PURRETTA + GALAXY.BWR.1;P777700 2 818(36) 20-Dec-82 16:40:58 PURRETTA + .DOC.1;P777700 3 1402(36) 14-Jan-83 22:30:20 WING + HSTNAM.TXT.1;P777700 4 9974(7) 12-Aug-82 14:36:51 PURRETTA + KS10.RAM.1;P777700 12 6144(36) 20-May-82 10:46:56 PURRETTA + .ULD.1;P777700 42 21186(36) 1-Mar-82 16:27:00 PURRETTA + MTBOOT.RDI.1;P777700 6 3072(36) 20-May-82 10:47:30 PURRETTA + NETSRV.RUN.1;P777700 1 234(7) 3-Nov-81 16:32:05 PURRETTA + PROGRAM-NAME-CACHE.TXT.1;P777700 1 78(7) 9-Mar-81 15:24:00 PURRETTA + PTYCON.ATO.1;P777700 1 333(7) 29-Mar-82 15:46:43 PURRETTA + REAPER.CMD.1;P777700 1 537(7) 22-Aug-79 19:53:37 PURRETTA + SMBOOT.EXE.1;P777700 3 1175(36) 13-May-82 12:17:47 PURRETTA + SMFILE.EXE.1;P777700 42 21504(36) 19-Feb-79 15:58:00 PURRETTA + .HLP.1;P777700 1 163(36) 19-Feb-79 15:42:00 PURRETTA + SMMTBT.EXE.1;P777700 3 1030(36) 13-May-82 12:18:15 PURRETTA + SYSJOB.EXE.1;P777700 7 3584(36) 20-Dec-82 17:43:24 PURRETTA + .HLP.1;P777700 3 5679(7) 29-Mar-82 15:47:02 PURRETTA + .RUN.1;P777700 1 340(7) 29-Mar-82 15:46:54 PURRETTA + SYSTEM.CMD.1;P777700 1 725(7) 8-May-79 14:46:35 PURRETTA + TOPS20.BWR.1;P777700 6 2669(36) 6-Apr-83 16:12:07 PURRETTA + .DOC.1;P777700 3 1255(36) 6-Apr-83 16:04:44 PURRETTA + + Total of 2685 pages in 33 files +$ ;;; For convenience, now is a good time to + ;;; make MONITR.EXE be your desired monitor! +$ren 2020-monmed.EXE.* (TO BE) monitr.exe + 2020-MONMED.EXE.1 => MONITR.EXE.1 [OK] +$^Ecease +1 ;;; OK, now we can try a disk boot. + +[System going down in one minute!!] +$ ;;; It really does take a minute. Patience... +[System down] + +Shutdown complete ;;; When you see this, type ^\ (CTRL-\) +[HALTED: FE interrupt] ;;; to return to KLH10 +KLH10> shutdown ;;; Then give "shutdown" command. +Continuing KN10 at loc 03... +**HALTED** +[HALTED: Program Halt, PC = 10754] +KLH10> q ;;; OK to quit KLH10 now! +Are you sure you want to quit? [Confirm] +Shutting down...Bye! +$ ;;; Back at unix shell prompt + + + +$ ./kn10-ks kst20.ini ;;; Fire it up again, with different ini file +KLH10 V2.0 alpha (MyKS) built Sep 30 2001 05:01:44 + Copyright © 1992-2001 Kenneth L. Harrenstien -- All Rights Reserved. + Proprietary and Confidential; the copyright notice does not indicate + actual or intended publication. +This software is provided under a license agreement described by the +accompanying file "LICENSE". USE OF THIS SOFTWARE CONSTITUTES +ACCEPTANCE OF THE TERMS OF THE LICENSE AGREEMENT. +This program comes "AS IS" with ABSOLUTELY NO WARRANTY. + +Compiled for LINUX on I386 with word model USEHWD +Emulated config: + CPU: KS10 SYS: T20 Pager: KL APRID: 759 + Memory: 1024 pages of 512 words (SHARED) + Time interval: INTRP Base: OSGET + Interval default: 60Hz + Internal clock: OSINT + Other: JPC DEBUG PCCACHE CTYINT IMPINT EVHINT + Devices: RH11 RPXX(DP) TM03(DP) +[MEM: Allocating 1024 pages shared memory, clearing...done] + +KLH10> ; This is a sample KLH10 config file for a KS10 running TOPS-20. +KLH10> +KLH10> ; Define basic KS10 device config - two RH11s each on its own Unibus +KLH10> +KLH10> devdef rh0 ub1 rh11 addr=776700 br=6 vec=254 +KLH10> devdef rh1 ub3 rh11 addr=772440 br=6 vec=224 +KLH10> +KLH10> ; Provide one disk, one tape in config T20 expects +KLH10> +KLH10> devdef dsk0 rh0.0 rp type=rp06 format=dbd9 path=T20-RP06.0-dbd9 iodly=0 +KLH10> [dprpxx: WARNING! sigpend 37, 40, 43, 44, 45, 46, 47, 48, 49, 63] +devdef mta0 rh1.0 tm03 fmtr=tm03 type=tu45 +KLH10> +KLH10> ; Define HOST device hackery if monitor supports it +KLH10> ;devdef idler 700 host +KLH10> +KLH10> ; For convenience, load up disk bootstrap +KLH10> load smboot-k.sav +Using word format "c36"... +Loaded "smboot-k.sav": +Format: DEC-CSAV +Data: 1162, Symwds: 0, Low: 040000, High: 044010, Startaddress: 040000 +Entvec: JRST (120 ST: 0, 124 RE: 0, 137 VR: 0,,0) +KLH10> [EOF on kst20.ini] ;;; Init file now done. +KLH10> go ;;; Boot is loaded, type "go" to start KN10! +Starting KN10 at loc 040000... + +BOOT> ;;; Type just CR, will default to MONITR.EXE +uba_read: Bad unibus IO addr: 760540 +uba_read: Bad unibus IO addr: 760010 + + +[PS MOUNTED] +uba_read: Bad unibus IO addr: 775400 +uba_read: Bad unibus IO addr: 777160 + +System restarting, wait... +ENTER CURRENT DATE AND TIME: 30-SEP-2001 0611 ;;; Must enter time AGAIN! + +YOU HAVE ENTERED SUNDAY, 30-SEPTEMBER-2001 6:11AM, + IS THIS CORRECT (Y,N) Y +WHY RELOAD? OTHER + +ACCOUNTS-TABLE.BIN NOT FOUND - ACCOUNT VALIDATION IS DISABLED +RUN CHECKD? Y ;;; "Y" for first time is good idea +[CHECKING FILE CONSISTENCY] + +[WORKING ON STRUCTURE - PS:] + + +LOCAL COUNT OF FILE PAGES: 8035 +LOCAL COUNT OF OVERHEAD PAGES: 5402 +LOCAL COUNT OF USED PAGES: 13437 + +SYSTEM COUNT BEFORE CHECKD: 13437 +SYSTEM COUNT AFTER CHECKD: 13437 + +THERE ARE NO LOST PAGES. + +******************** +*BUGINF "UXXFIT" AT 30-SEP-2001 06:11:10 +*CHECKPOINT FILE NOT IN CORRECT FORMAT FOR THIS SYSTEM, REBUILDING... +*JOB: 0, USER: OPERATOR +******************** +uba_read: Bad unibus IO addr: 760540 + +RUNNING DDMP + + +SYSJOB 5(20) STARTED AT 30-SEP-2001 0611 +RUN SYS:ORION +RUN SYS:QUASAR +RUN SYS:MOUNTR +RUN SYS:INFO +RUN SYS:MAILER +RUN SYS:MAPPER +RUN SYS:LPTSPL +RUN SYS:LPTSPL +RUN SYS:CDRIVE +RUN SYS:SPRINT +JOB 0 /LOG OPERATOR XX OPERATOR +ENA +^ESET LOGIN PSEUDO +^ESET LOGIN CONSOLE +^ESET OPERATOR +PTYCON +GET SYSTEM:PTYCON.ATO +/ +JOB 1 /LOG OPERATOR XX OPERATOR +ENA +RUN SYS:BATCON +/ +SJ 0: @LOG OPERATOR OPERATOR +SJ 1: @LOG OPERATOR OPERATOR +SJ 0: JOB 1 ON TTY43 30-SEP-2001 06:11:20 +SJ 0: @ENA +SJ 0: $^ESET LOGIN PSEUDO +SJ 0: $^ESET LOGIN CONSOLE +SJ 0: $^ESET OPERATOR +SJ 0: $PTYCON +SJ 1: JOB 2 ON TTY44 30-SEP-2001 06:11:21 +SJ 1: @ENA +SJ 1: $RUN SYS:BATCON +SJ 0: PTYCON> GET SYSTEM:PTYCON.ATO +SJ 0: PTYCON> SILENCE + +[From OPERATOR on line 45 to all: SYSTEM IN OPERATION] +SJ 0: PTYCON.LOG.1 +SJ 0: PTYCON> W ALL +SJ 0: OPR(0) 3 OPERATOR OPR TI 0:0:0 +SJ 0: PTYCON> CONN OPR +SJ 0: [CONNECTED TO SUBJOB OPR(0)] + ;;; When things seem quiet, type CR to get an EXEC. + + TOPS-2020 MEDIUM SYSTEM, TOPS-20 Monitor 4.1(5471) +@TER NO RAI +@log operator ;;; Try logging in, pwd "DEC-20" + Job 4 on TTY42 30-Sep-2001 06:11:53 +@ena +$ ;;; Now do whatever you wish... + + + ===================================================== + +Other things you probably want to do at some point: + + +$copy tty: monnam.txt ;;; Set the system name +MyKS TOPS-20 System +^Z +$copy tty: 4-1-config.cmd ;;; Set timezone here for now +timezone 8 +^Z +$^Ecreate ;;; Change operator password +$$password tqbfjotld ;;; (use something better) +$$ +$^Ecreate ;;; Create your account +$$password nittfagmtc2taotp ;;; (or something better) +$$wheel +$$working 10000 +$$permanent 10000 +$$ +$ + +There are large manuals devoted to TOPS-20 system maintenance, but +this is enough to make you dangerous. + +KS T20 BOOT BUG NOTES +===================== + +BOOT +---- + The magtape bootstrap source is BOOT.MAC, which was still used +to build both KL and KS bootstraps as of V4.1 of TOPS-20 (but at some +point thereafter, and certainly as of V6.0, there were big changes -- +the KS code no longer exists in the last TOPS-20 BOOT.MAC). + For the KS10, BOOT is assembled into either SMBOOT.EXE (for +booting from disk) or SMMTBT.EXE (for booting from tape), both in +CSAVE format. There is also SMBOOT.I?? which is SMBOOT.EXE converted +into FE-loadable format, which the KLH10 does not use. + +Unfortunately the KS10 BOOT cannot be used as-is without either +patching it or playing tricks in the emulator. It has at least +three bugs: + + [1] The most serious is the following: + 40120/ J3+6/ WRCSTM [77B5] + should be: WRCSTM [77B5]-ENT(16) + (or better yet set up with a MOVSI 1,770000 ? WRCSTM 1) + + This fails because at the time it is executed it has been + moved into high core and the literal is no longer at that + address! It ends up loading the CST mask with 0 which + causes an infinite page failure loop when the code turns + paging on for the first time. + + It is not clear how a real KS10 managed to get past this bug. + + For now, this has been patched: + 40120/ 702540,,40127 => 702540,,774127 + (Start address remains 40000.) + + [2] In the code starting at FNDDEV there is the instruction + SETZM KLIOWD (zeros loc 35, KLINIK output word) + which causes a page trap because at this point page 0 is unmapped. + This instruction should either be done before turning paging on, + or after low pages have been mapped. + + It was probably never noticed because the page fault handler + merely continues at .+1. + + [3] The disk bootstrap (SMBOOT) also has a bug of its own: + CHKCHN: CAIG P,MAXCHN1 (CAIG 17,2) + should be: CAIG P1,MAXCHN-1 (CAIG 10,1) + + Note there are two independent typo bugs in that one instruction! + Geez. First, the compare to P instead of P1 is clearly wrong. + Second, MAXCHN1 is undefined; the assembler does not distinguish + symbols beyond the first 6 characters so MAXCHN is silently being + used, with a value of 2. This causes the loop to check 3 channels + (0,1,2) but only 2 channels are provided for in the RH11CH table! + Trying to check the 3rd channel results in referencing the wrong + words of a valid channel, or worse. + + This probably worked in real life because the first RH11 + always existed and was always used for the boot. + + For now, this has been patched: + SMMTBT: 41335/ 307740,,2 => 307400,,1 + SMBOOT: 41441/ 307740,,2 => 307400,,1 + +FIXED VERSIONS +-------------- + +The patched versions of these binaries have been dumped out as: + + SMBOOT: smboot-k.sav (c36 CSAVE format) + SMMTBT: smmtbt-k.sav (c36 CSAVE format) + + +MONITOR BOOT BUG +---------------- + + There is a T20 monitor bug with the G/143 boot code that +prevents simply loading monitr.exe directly into the KLH10 and +starting it. + When the monitor gets around to executing its first PXCT at +KISLOD+2 to set up the user ACs, it actually clobbers its own ACs. +The reason for this is that no WRUBR was ever done to set the +previous-context AC block; the emulator initializes both contexts to +AC block 0. Several WRUBRs have been executed prior to this, but none +of them ever set the AC block fields. One of them (at PGRON+3) +doesn't even set the base address either; they mostly seem to be used +as the CLHWPT macro to clear the cache and paging hardware, and refer +to an argument of KIPGW0 or KIPGWD which in the source (APRSRV.MAC) +don't have the set-ac-block flag set. + This appears to have worked in the past because BOOT, which +is expected to load monitr.exe, has already set the previous-context-AC +block to 6. + For now, this can be avoided by always using SMBOOT or SMMTBT. +One solution would be for the KLH10 FE to provide a "monitor load" +command that did this previous-context AC block setting (and any other +setup that might be found necessary) in addition to doing a normal +binary load. + +KS T20 MONITOR BUG +------------------ + + There seems to be a timing race condition when outputting to +the CTY. Normally the KLH10 responds instantly to a KS->FE interrupt +request by gobbling a valid output char and sending a FE->KS interrupt. +This causes output to wedge after the first bufferful until another +FE->KS interrupt is given. + This happens because of the code in the tty driver that +tries to turn interrupts off and then on while initiating output of the +first char. It actually doesn't do any good and the entire buffer is +emptied at interrupt level before the first call returns! + + +KS T20 MONITOR SOURCE GLITCH +---------------------------- + + My copy of the "Monitor Sources" tape has a bad record data +header which resulted in problems with the "sources.cmd" file; it +either has its last page clobbered or has 444 zero words appended. + Shoppa's copy may not have this problem. Fortunately it isn't +an essential file. diff --git a/doc/news.txt b/doc/news.txt new file mode 100644 index 0000000..347c695 --- /dev/null +++ b/doc/news.txt @@ -0,0 +1,39 @@ +/* NEWS.TXT - KLH10 Change Summaries and other news +*/ +/* $Id: news.txt,v 2.1 2001/11/19 12:13:12 klh Exp $ +*/ +/* Copyright © 2001 Kenneth L. Harrenstien +** All Rights Reserved. +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +*/ + +KNOWN BUGS +========== + +Still outstanding: + + - KS TOPS-10 7.03 install from tape periodically hangs on MT. + - KS TOPS-10 7.03 disk boot produces: + KLH10 PANIC: rh11_iobeg: drv/ctrlr mismatch: 7/0 + +(Of course, this will never be a complete list!) + + +KLH10 CHANGES +============= + +KLH10 2.0A: 19-Nov-2001 + - Revised ARP code in dpimp.c to fix problems with Linux port; + ITS networking now supported on Linux! + - Cleaned up Solaris port. Two Makefiles now provided, one for + GCC and the other for SUN C. Compiles and runs, but actual network + operation not yet verified. + - Fixed bug with writing TPS format tape images (caught by John Wilson). + - Added "port-ks" target to Makefile to help porters. + +KLH10 2.0: 10-Nov-2001 + Initial Distribution release. diff --git a/doc/usage.txt b/doc/usage.txt new file mode 100644 index 0000000..0a18650 --- /dev/null +++ b/doc/usage.txt @@ -0,0 +1,311 @@ +/* USAGE.TXT - KLH10 Usage Instructions +*/ +/* $Id: usage.txt,v 2.3 2001/11/10 21:24:21 klh Exp $ +*/ +/* Copyright © 1994-1999, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +*/ + + This file provides basic pointers for using the KLH10. +See "install.txt" for information on installing and configuring the KLH10. + +KLH10 STARTUP: + + You should not start the KLH10 unless you have installed it in +its "home directory" and are connected to that directory (see the file +"install.txt" if necessary). Then simply give the following shell +command: + % klh10 + +The KLH10 will start up, allocate and clear KN10 memory, and begin +reading commands from the default file "klh10.ini". If you wish to +use a different init file, you can specify it on the command line, for +example: + % klh10 test.ini ; Use "test.ini" instead of "klh10.ini" + % klh10 /dev/null ; Use nothing + +Or you can simply edit or delete "klh10.ini" itself. + + +KLH10 COMMAND PARSER: + + After starting the KLH10 you will be talking to the command +parser. This serves a function very similar to that of the RSX20F +PARSER front-end program for a real KL10. However, the KLH10 commands +are nothing like PARSER commands. For a list of the commands, see +the files "cmdsum.txt" and "cmdref.txt". + + Normally the only reason you will be talking to the command +parser is to get the KN10 configured, load a bootstrap program, and +start it; from there on you interact only with the KN10. This can all +be done automatically by commands in the "klh10.ini" file, and it is +highly advisable to at least configure the KN10 devices this way, +using the information in "install.txt". + + +KLH10 BOOTSTRAPPING: + + The KLH10 is capable of loading PDP-10 binary files directly +into its KN10 memory with the "load" command. The KLH10 uses the +native OS filesystem as its "front-end filesystem", and does not +require a special area on a virtual disk. Ordinarily you will use +this capability to load a PDP-10 bootstrap program, which when started +will then load either the TOPS-10 or TOPS-20 monitor. For example: + + KLH10> load boot.exe + +will load "boot.exe" from the native filesystem, producing this output: + + Using word format "c36"... + Loaded "boot.exe": + Format: DEC-CSAV + Data: 4630, Symwds: 0, Low: 040000, High: 054641, Startaddress: 040000 + Entvec: JRST (120 ST: 00, 124 RE: 00, 137 VR: 0,,0) + KLH10> + +Once the bootstrap is loaded, you can start it by giving the +"go" command: + + KLH10> go + Starting KN10 at loc 040000... + + BOOT V11.0(315) + + BOOT> + +And from this point on, you are talking to the PDP-10 software, and the +remaining procedures for bringing up the monitor should be the same as +for a real system. In particular, typing CR should spur the bootstrap +into loading and starting the default of MONITR.EXE. + + Note that there are two TOPS-20 boot programs included with +the KLH10 distribution: "boot.exe" which boots from disk, and +"mtboot.exe" which boots from tape. There is also a TOPS-10 boot +program, "klboot.exe". These are exact copies of the Digital +bootstraps by those names, provided only as a convenience to users who +already have a TOPS-10 or TOPS-20 license. + + The details of a complete boot and filesystem rebuild from +tape, using "mtboot.exe", are too intricate for a few paragraphs. +Suffice to say here that once you get to the BOOT> prompt of MTBOOT, +you can proceed just as for a real KL10, with the exception that +building a "front-end filesystem" area is not necessary. See the file +"klt20.txt" for an example of a TOPS-20 system installation. + + +KLH10 COMMANDS vs KN10 EXECUTION: + + It is very important to understand that the KLH10 is always +doing only one of two things. It is either reading and executing +commands typed to it, or it is running the KN10 which is executing +PDP-10 instructions. There is no way to enter KLH10 commands without +temporarily suspending KN10 execution. + + While the KLH10 is reading commands, nothing else is +happening, and your typein is seen only by the KLH10 command parser. +The KN10 does not run until you give an explicit command to start or +resume execution. + + While the KN10 is running, your typein is seen only by the +PDP-10, as CTY input. You can return to the KLH10 command parser at +any time by typing the command escape character (^\, CONTROL-\, ASCII +"FS", octal 034), which suspends KN10 execution. Control also returns +to the command parser if the KN10 halts for any reason. + + Future versions of the KLH10 may allow concurrent execution so +that KLH10 commands can be given while the KN10 continues to run, +similar to the way a real KL system works with its front-end PDP-11 +independent of the KL10 CPU. For now, simplicity is stability. + + +STOPPING THE KLH10: + + Normally, you will be running a PDP-10 monitor. Bring it down +as you would for a real KL10, except that at the point where you type +^\ to return to the RSX20F PARSER, instead you are returned to the +KLH10 command parser and the KN10 is stopped. You can give the +"shutdown" command, which deposits -1 in location 30 and continues +the KN10; this should cause the monitor to execute a HALT instruction, +which returns control back to the KLH10 command parser. + + You can then give the "quit" command (which asks for +confirmation) to leave the KLH10 program completely. This command +performs essential cleanups of the various device processes and shared +memory areas, and should always be done. + + Alternatively, if the monitor is wedged or you don't care +about the PDP-10 filesystem consistency, you can blow everything away +immediately by returning to the command parser with ^\ and using "quit" +right away. + + A word about restarting. Although it may work to use "reset" +and then load the PDP-10 bootstrap in order to restart a monitor without +actually quitting the KLH10 program, this is not recommended; it is much +safer to quit entirely and invoke the KLH10 again from scratch. + + +IF THE KLH10 CRASHES: + + If the KLH10 process dies accidentally or is killed in any way +other than via the "quit" command, then you will probably find some +leftover device processes (DPs) littering your host system. If this +happens, you can clean up as follows: + + Use the shell command "ps ax" (on Solaris, "ps -eaf") to find + the processes. + They will be called "dprpxx", "dptm03", or "dpni20". + You will notice a -DPM:<#> argument to the DP; this + identifies the shared memory segment ID it's using. + Kill each DP with "kill " where is the process ID. + Find the active shared memory segments with "ipcs". + Those with segment IDs belonging to the killed processes + (the numbers in the -DPM: args) + must be flushed; use "ipcrm -m <#>" for each one. + Yes, this is tedious, isn't it? Blame AT&T SYSV's moronic IPC + facilities. And next time, use "quit" if you can... + +RUNNING THE KLH10 IN BACKGROUND MODE: + +As of V1.1 the emulator can now be run as a background process, which +for example makes it possible to start it up automatically, without +manual intervention, whenever the hardware system is rebooted. + +This feature is very simple but has the disadvantage that once the +background process has started, you cannot talk to the emulator's CTY. + +The procedure is as follows: + + (1) Create a klh10.ini file for monitor auto-start, perhaps distinguishing + it with a different filename such as klh10.ini-q. + The exact details will vary depending on the monitor; for TOPS-20 + an auto-start can be done as follows: + + KLH10>load boot.exe + KLH10>dep 0 400000,,0 + KLH10>go 40001 + + (2) Invoke the KLH10 using the "-background" switch and auto-start + initialization file, with output redirected to a log file, and + running in the background. + Examples: + + Bourne Shell (/bin/sh): + $ ./klh10 -background klh10.ini-q > logfile 2>&1 & + + C Shell (/bin/csh, /bin/tcsh, etc): + % ./klh10 -background klh10.ini-q >& logfile & + + Note and remember the process ID that the shell prints out, for use + when bringing the system/emulator down. + +In order to bring down the system and emulator when running in the +background: + + (1) Bring down the monitor in the usual way (e.g. on TOPS-20 you + would use the ^ECEASE command). + + (2) Find the process ID of the emulator if you didn't already note it, + by using "ps". + + (3) Terminate the emulator by sending a SIGTERM signal, e.g. + % kill + +Check the log file to make sure that everything went OK. This file +simply records all output to the emulated CTY. If you wish to observe +it while the emulator is running, do "tail -f logfile". + +Again, UNIX unfortunately provides no mechanism for re-attaching a +terminal to a background process, so there is currently no way to provide +input to the emulated CTY in this situation. Eventually this feature +will be provided by a front-end daemon that can be connected to at will. + + +USING VIRTUAL TAPES: + + A useful feature of the KLH10 is its "virtual tape" mechanism, +which allows PDP-10 programs to believe they are reading and writing +magtapes when in fact they are merely doing I/O to a disk file in the +native filesystem. This is very fast and convenient. + +Unfortunately, the process of "mounting" and "unmounting" virtual +tapes is not as convenient as it should be, and is the major interface +quirk that remains to be fixed. What follows documents the current +procedure, warts and all. This should be read in conjunction with the +descriptions of DEVMOUNT and DEVUNMOUNT in the "cmdref.txt" file, as +well as "vtape.txt" for further details. + +To CREATE a virtual tape, use the DEVMOUNT command with the + name of a non-existent file and the parameter MODE=CREATE + (or just "RW"; for the full list of parameters see cmdref.txt). + When done writing and reading the tape, use DEVUNMOUNT to + "unmount" it from the virtual drive and finalize the tape file(s). + +To READ an existing virtual tape, use DEVMOUNT with the + filename of the existing tape file; it will default to MODE=READ + which will make the drive appear to be write-locked. + +For safety, the tape code will not allow you to mount an existing +virtual tape for writing (a mode known as "update"). If you try to do +this, you will see something like the following: + + @[HALTED: FE interrupt] ; ^\ typed at T20 EXEC + KLH10> devmount mta0 klhbackup rw ; Try to mount existing tape + DPTM03: vmt_xmount: Tape file "klhbackup" already exists + [dptm03: Couldn't mount read/write virtual tape: klhbackup] + Mount requested: "klhbackup" + KLH10> + +Note that the command parser doesn't have a clean way to know if the +mount request succeeded or failed; it can only say that a request was +made. Any failure or success reports come directly from the tape device. + + +USING HARDWARE TAPES: + + The KLH10 has the capability of using physical ("hard") tape +drives to read and write actual, not virtual, tapes. Normally a hard +mount will be made at the time the tape device is first defined with +DEVDEFINE, but it is also possible to use DEVMOUNT to change this +later. + +Usage is the same as for virtual tapes, except that in the DEVMOUNT +command, the option "hard" must be specified: + + @[HALTED: FE interrupt] + KLH10> devmo mta0 /dev/rmt0a hard + Mount requested: "/dev/rmt0a" + KLH10> + +Do NOT use either the block (/dev/mt0a) or non-rewind (/dev/nrmt0a) +forms of the physical device pathnames. No other options are +recognized when "hard" is set; they will be ignored. In particular, +"ro" has no effect, because the binding established by a hard mount is +between the KN10 device and an external device, not between the KN10 +and a particular tape. In order to write-lock physical types, you +must use their physical write-lock mechanism. + +In general physical tapes can be loaded and unloaded from the drive +manually and all should operate as if the drive was a real TU45, TU77, +or whatever. My favorite example of this is the "UNLOAD" command in +the TOPS-20 EXEC, which will eject the physical tape if the host +system supports that capability. + +An error message will be printed if any problem is detected; for +example, the following can happen if the drive is powered off: + + @[HALTED: FE interrupt] + KLH10> devmo mta0 /dev/rmt0a hard + Mount requested: "/dev/rmt0a" + KLH10> [dptm03: Cannot mount device "/dev/rmt0a": I/O error] + KLH10> + +When using hardware drives, there may be some lag between the time of +physical events (like going offline or online) and the time the KN10 +finds out about the new status. This is primarily due to the overly +thick insulation that the native OS interposes between the KN10 and +the actual hardware. diff --git a/doc/utils.txt b/doc/utils.txt new file mode 100644 index 0000000..7dfba35 --- /dev/null +++ b/doc/utils.txt @@ -0,0 +1,245 @@ +/* UTILS.TXT - KLH10 Utilities +*/ +/* $Id: utils.txt,v 2.2 2001/11/10 21:24:21 klh Exp $ +*/ +/* Copyright © 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +*/ + +This file attempts to provide a quick guide to the various utility +programs provided with this Distribution (or its Auxiliary +Distribution). There are no man pages nor GnuEmacs INFO files. + +In rough order of utility: + +wxtest - Test word10.h macros (used only to verify build configuration) +wfconv - 36-bit Word File conversion +tapedd - Tape copy & conversion (real or virtual) +vdkfmt - Virtual Disk initialization and conversion +enaddr - Ethernet interface configuration +read20 - (TOPS-20) Read & extract DUMPER format tapes & tape images +uexbconv - (TOPS-20) Convert .EXB files to .SAV format +udlconv - (ITS) Convert Alan's DIR.LIST into an ITSDUMP virtual tape +supdup - (ITS) SUPDUP client and server for Unix + +More details, in alphabetical order: + +ENADDR +------ + +Usage: enaddr [-v] [ [default | ] [+] [-]] + -v Outputs debug and config info for all interfaces + Specific interface to read or modify + default Reset ether addr to HW default, if known + Set ether addr to this (form x:x:x:x:x:x) + + Add multicast addr (same form) + - Delete multicast addr (same form) + + This program exists primarily to help test and verify the +operation of some basic osdnet.c code while porting. In theory it is +not needed to run a correctly configured KN10 if the osdnet.c +implementation is correct, but is provided as a utility because its +manual functions have been useful in the past, especially when getting +non-IP protocols like DECNET to work. + +This can be built individually with "make enaddr". + +READ20 +------ + +Usage: read20 [switches] [patterns] + Switches must be separated. + Patterns are simple substrings of the filenames to select. + -f Specify tapefile. '-' uses stdin. Default is /dev/rmt8 + -x Extract files + -t List contents (one of -t or -x must be given) + -tl Show tape locations in listing + -S Only process saveset + -e Only process filenames matching (one -e only) + -F ... Only process files numbered ... + -V Show FDB info for files extracted or listed + -s Start reading at this byte loc in tapefile (seeks) + -q Say using QIC (1/4") cartridge tape + -v Verbose feedback + -g Keep generation # in extracted filename + -n Use numeric filenames for extracts, starting with + -W Treat all files as 36-bit. Otherwise, 7-bit files + are treated as ascii, 8-bit as 8-bit binary, and + all others as 36-bit (direct copy of tape data). + -T Treat 0 or 36-bit files as 7-bit ascii + -B Treat 0 or 36-bit files as 8-bit binary + -c Keep CRs in CRLF pairs for ascii files + -d Debug level (>0,>5,>10,>99) (default 0) + + This program is part of the Auxiliary Distribution and the +source lives in the "contrib" directory. It appears to have +originated with Jim Guyton and was further modified by Jay Lepreau, +Charles Hedrick, Stu Grossman, and possibly others before undergoing a +substantial upgrade by Ken Harrenstien. Other versions may still +exist. + +READ20 is used to list and extract files on Unix from a TOPS-20 DUMPER +tape image. This can be either a real tape, read directly as a +sequence of records, or a virtual tape image using the KLH10 RAW +format. It does NOT directly handle any other virtual tape formats, +but could do so indirectly by using the TAPEDD utility as an input +filter. + +No Makefile is provided; just compile read20.c with your +favorite C invocation and install the binary. e.g. + + cc -O -o read20 read20.c + +SUPDUP +------ + + This is of interest mainly to ITS users. SUPDUP is a protocol +similar to TELNET which was primarily supported by ITS. However, at +least one Unix server was written. + + This program is part of the Auxiliary Distribution and the +source lives in the "contrib" directory. It was originally written +by David Bridgham and modified by a few people since then. + +I have only used the client program from this package, not the server. +You will have to hope that the Makefile included with it works for you, +or modify it to suit. + +TAPEDD +------ + +Usage: tapedd + itX= (Required) Input Tape device, where 'X' is optional + drive spec: (defaults to 'h') + h - Half-inch magtape drive (default) + q - QIC (quarter-inch cartridge) drive + 8 - 8mm drive + 4 - 4mm DAT drive + vF - Virtual tape & drive, where 'F' is optional + format spec: (defaults based on file extension) + r - Raw format, paired with control file + s - TPS format + e - TPE format + c - TPC format + i - (read-only) ITS DUMP tapedir + otX= (Required) Output Tape device, X as above + {i,o}c= alternate tape Control file (old id=,od=) + {i,o}f= alternate raw data file + {i,o,}bs= Block size (record length) + log= Log filespec (defaults to stderr) + rskip=<#> Skip # input records + fskip=<#> Skip # input files/tapemarks + rcnt=<#> Max # records to write + Fcnt=<#> Max # files/tapemarks to write + peot Use physical EOT, ignore logical EOT (double tapemark) + test Parse control file, output result to stdout + verbose Verbose + + TAPEDD is a tape version of DD, used to copy magtapes. +Normally it will convert them from one format to another in the +process; for example, from a physical magtape to a virtual tape image, +in a variety of formats. + +This can be built individually with "make tapedd". + +UDLCONV +------- + +Usage: ./udlconv [switches] < DIR.LIST > DIR.tpk + -p - Prefix this path to all host filenames + + This is of interest only to ITS users. + +Standard input is assumed to be a DIR.LIST file from Alan Bawden's ITS +archives. The resulting standard output is a tape-control (.tpk) file +in ITSDUMP format, suitable for mounting as a virtual tape. + +The reason this is far preferable to unpacking the files and FTPing them +into ITS is because this method preserves all of the available file +meta-information (symlinks, creation timestamps, authors, etc). + +Note that the output can be fed into TAPEDD to generate a tape image +in some other format, if desired. + +This can be built individually with "make udlconv". + +UEXBCONV +-------- + +Usage: uexbconv [-v] < infile.exb > outfile.sav + (input file must be in core-dump (C36) format, as the + output file will be.) + + The KL10 FE is a PDP-11 that stores its PDP-10 binaries, +particularly the bootstraps "boot.exe" and "mtboot.exe", in a format +called "RSX-BINARY". These files sometimes exist on the KL filesystem +with the extension .EXB. + +The KL10 program RSXFMT.EXE converts from .SAV to .EXB but does not +furnish the opposite conversion; hence this utility, which was used to +obtain some of the KL10 bootstrap images. + +This can be built individually with "make uexbconv". + +VDKFMT +------ + +Usage: vdkfmt + ip= Input disk device/file: + op= Output disk device + ifmt= format of input pack data + ofmt= format of output pack data + dt= Type of drive (RP06, etc) + log= Log filespec (optional, defaults to stderr) + verbose Verbose (optional) + + This utility is similar to TAPEDD; it is used to copy virtual +disk images from one format to another. It is rarely needed, but a +lifesaver when it is. + +This can be built individually with "make vdkfmt". + +WFCONV +------ + +Usage: wfconv -io outfile + where 'i' and 'o' are chars specifying the input and output formats: + c - Core-dump (std tape format, 4 8-bit, 1 4-bit bytes = 36 bits) + h - High-density (FTP 36-bit image, 9 8-bit bytes = 72 bits) + a,7 - Ansi-Ascii (4 7-bit, 1 8-bit byte = 36 bits) + s,6 - Sixbit (6 6-bit bytes = 36 bits) + u - Unixified (Alan Bawden format, various = 36 bits) + t - Text-Newline (CRLF-NL conversion; 5 7-bit bytes = 35 bits ONLY) + d - Debug (output only - show word values) + D - Debug (like -d with offsets) + Note: EOF on input always zero-pads up to a PDP-10 word boundary. + + This is one of the handiest utilities, used to convert PDP-10 +word data from one representation to another. No filenames are given; +it always reads from standard input and writes to standard output. + +The format names are similar to those for magtape formats. + +This can be built individually with "make wfconv". + +WXTEST +------ + +Usage: wxtest -[qvh] + -q Quiet + -v Verbose + -h Help (this stuff) + + This "utility" is actually a diagnostic used to verify that a +particular port configuration is correctly defining the macros used to +manipulate PDP-10 words. While most people will never need to worry +about such things, this is an extremely useful regression test when +porting to a new machine architecture or trying out a new method of +representing a PDP-10 word. + +This can be built individually with "make wxtest". diff --git a/doc/vtape.txt b/doc/vtape.txt new file mode 100644 index 0000000..35518ba --- /dev/null +++ b/doc/vtape.txt @@ -0,0 +1,493 @@ +/* VTAPE.TXT - KLH10 Virtual Tape Format +*/ +/* $Id: vtape.txt,v 2.3 2001/11/10 21:24:21 klh Exp $ +*/ +/* Copyright © 1994-1999, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +*/ + + One of the methods used by the KLH10 to emulate PDP-10 +magnetic tape devices is to provide support for "virtual tapes". +These virtual tapes are in reality disk files on the native host +system which have a specific format. Because they are native disk +files, it is far easier and faster to manipulate these virtual tape +images (for reading, writing, copying, archival, and transport over +networks) than would be the case for real physical tapes. + +The purpose of this description is to allow users to generate or +maintain virtual tapes using their own tools in addition to the ones +provided in the KLH10 distribution. In most cases these tools can be +nothing more than a text editor and a simple copy program such as Unix +"dd". + +VIRTUAL TAPE FORMATS +==================== + + Several virtual tape formats exist, developed more or less +independently. The KLH10 software can handle most of them and others +could be added if necessary. These formats are: + + "RAW" - original KLH10 format + "TPS" - Wilson & Supnik format + "TPC" - DECUS/Shoppa format + "ITSDUMP" - special KLH10 format for ITS + + +"RAW" VIRTUAL TAPE: Directory and Data files +================== + + Each RAW-format virtual tape consists of two native files, one +for the actual tape data and the other to describe that data. For +example, a virtual tape with the name "kosh" would consist of these +two files: + + Virtual tape "kosh": + kosh - Tape directory (text; defines structure of data) + kosh.tap - Tape data (binary; one byte per tape frame) + + +"RAW" DATA FILE FORMAT (.TAP) + + The tape data file is simply a continuous sequence of 8-bit bytes +corresponding to the logical stream of 8-bit tape data frames. There is +nothing in this file except tape data; there are no record boundaries or +tapemarks. Because this is a binary data file, all operations on this +file must preserve the data exactly. + + +"RAW" DIRECTORY FILE FORMAT (.TDR) + + The tape directory is a text file that describes the contents of +its associated tape data file; it defines how the tape is structured in +terms of record boundaries, tapemarks, length, and internal format. It +can be used to describe anything about the tape in an easily readable +and editable form. + + The directory is formatted as a set of logical text lines. Each +line begins with a keyword, optionally followed by data. Comments are +introduced by a semi-colon character (';') and continue to the end of +that physical line. Blank lines are ignored. Whitespace is also +ignored except at the start of a line, where indentation may be used +only when continuing a logical line over several physical +lines. + + Keywords: + TF-Format: [] + ; Tape datafile format and optional filename + <#>: ; File/Record descriptors + ; optional continuation of above + EOT: ; Physical End of Tape, remaining text ignored. + + - REQUIRED. Describes how the tape file data is stored on + disk. Currently only one keyword is fully supported: + "raw" - 8-bit bytes, one per frame. + + - DEPRECATED. Optional name of tape data file, a feature + no longer used (ie ignored on input, not generated on output). + The tape data filename is either specified at mount time or + dynamically generated using ".tap" as an extension to the name of + the tape directory file. This extension replaces ".tdr" if it + exists, otherwise ".tap" is simply appended. + + <#> - location in data file of 1st record beginning next file. For "raw" + format this corresponds to the logical frame # on tape as well, but + for other formats this may serve a realignment purpose. + The string "BOT" is considered equivalent to "0". + + - a list of record descriptors composing the file, + separated by whitespace. The list may be empty, as is normally + the case for two consecutive tapemarks that signal a logical EOT. + + - A record descriptor, with the format + [*][E[]] + where: + - decimal record length, in frames (8-bit bytes) + - # times this record length recurs. + E - indicates error when reading this record (or the last of a + series). is an optional # and specifies additional + information, if any, as to nature of the error. + + The string "EOF" is a special that signals a tape mark. + + +TAPE DIRECTORY EXAMPLE: + + Here is an example of a tape directory file for a small dump: + + ; DUMPER saveset of PS: ; Human-readable comment + TF-Format: raw ; Required, and must be "raw" + 0: 2590*8407 EOF ; First file + 21774130: EOF ; Second file (empty == logical EOT) + EOT: ; Physical EOT + +Note that comments can exist, that the data file format is RAW, and +that it starts at frame 0 with a 2590-byte record which is repeated +8407 times, followed by an EOF tapemark, then another EOF tapemark. +The emulator software generates the second EOF using a new line so +that the tape location (21774130) can serve as a validity cross-check; +this is a compromise between putting everything on a single line or +putting each record on its own line, either of which is equally valid. + +"TPS" FILE FORMAT (.TAP, .TPS, .TPE) +==================================== + + This is the format used by Bob Supnik's SIMH emulators. It is +almost the same as John Wilson's E11 format, which it was intended to +resemble. + +A TPS file is assumed to consist of 8-bit bytes. + +Each record starts with a 4-byte header in little-endian order. +The high bit of this 32-bit header is set to indicate an error of +some kind (similar to the 'E' flag in a RAW-format control file) and +the remaining 31 bits contain the record length N. + +This header is followed by N data bytes plus, if N is odd, a padding +byte (of undefined value) at the end. Following this is a copy of the +4-byte record header. + +Tapemarks are represented by a single header with N=0. + +The reason for having the record length both before and after the +record data is so tape motion in the reverse direction can be more +easily simulated. + +The E11 (.TPE) format is identical except there are no padding bytes. + + +"TPC" FILE FORMAT (.TPC) +======================== + + This format is not as flexible as the others but was +apparently quite commonly used for DECUS tape images, and Tim Shoppa +has a "couple thousand" TPC images stashed away. + +A TPC file is assumed to consist of 8-bit bytes. + +Each record starts with a 2-byte header in little-endian order, +containing 16 bits of record length N. + +This header is followed by N data bytes plus, if N is odd, a padding +byte (of undefined value) at the end. Unlike TPS format, there is no +trailing header. + +Tapemarks are represented by a single header with N=0. + +Obviously it is difficult to use this format directly when reverse +tape motion is desired; an internal representation must be built. + + +TAPE GENERATION + +There are three principal ways to create virtual tapes: + + (1) Using the emulator to write tapes. + (2) Copying from real PDP-10 tapes. + (3) Creating synthesized tapes "by hand". + + +TAPE GENERATION [1]: Using the emulator +======================================= + + Method [1] is described in the documentation for the emulator +proper; see the section "Using Virtual Tapes" in USAGE.TXT. + + +TAPE GENERATION [2]: Copying a real tape +======================================== + + This is likely to be the most common use of virtual tapes for a +new installation. This is also the only way to move data in and out of +a virtual system if there is no network support. + +The simplest method for copying a real tape is to use the TAPEDD utility +provided with the emulator, as follows: + + + + % tapedd it=/dev/rmt0a otv=kosh + +This will copy the tape into a virtual tape named "kosh" with whatever +extension is appropriate for the format. Note that the TAPEDD +arguments have a form similar to that of the standard Unix DD utility, +but are not the same. Invoking TAPEDD without arguments will cause it +to list the possible options, most of which are never needed. + +If you want to copy a virtual tape back onto a physical tape, then +the counterpart to the above command would be: + + % tapedd itv=kosh ot=/dev/rmt0a + +If for some reason TAPEDD is not available or you want to build a virtual +tape that isn't a direct copy of an existing physical tape, you will need +to get your hands dirty with method [3]. + +TAPEDD allows specifying the virtual tape format with a letter following +the "v". For example: + + % tapedd it=/dev/rmt0a otvs=kosh + +will copy a tape into a TPS format file called "kosh.tps". + + +TAPE GENERATION [3]: Creating a synthesized tape +================================================ + + One of the primary reasons for implementing virtual tapes as a +RAW-format pair of files (data and directory) was to simplify the +process of creating and managing these tapes by hand in an extremely +portable way, without using any special software tools. + +To generate the tape directory file, all you need is a text editor. + +To generate the tape data file, normally it suffices to use the Unix +utility "dd" or equivalent device-to-device copying program. You simply +need to append every byte of tape data into a sequential binary file. + +For example, the tape data file corresponding to the sample tape +directory above could have been read with a command like this: + + % dd if=/dev/rmt0a of=kosh.tap bs=126b + 0+8407 records in + 0+8407 records out + +Note that to use this technique properly you must have some prior +knowledge of the record sizes on the tape. For one thing, the blocksize +specified must be larger than the largest possible record size, to avoid +record truncation (hence the bs=126b); but more importantly, without +knowing the record size you don't know what to put in the tape directory +(TDR) file. In this case, we know that TOPS-20 DUMPER records are <518 +words * 5 bytes/word> = 2590 bytes and so the appropriate is +2590*8407. But if the records were of different lengths then DD +provides no way for you to tell what these lengths were; this is one +reason TAPEDD is easier. + +[Aside: TOPS-20 DUMPER could actually use a blocking factor of up to 15, +so that records could be 15*518*5 = 38850 bytes long, but for virtual +tapes the blocking is irrelevant and a factor of 1 always works for +DUMPER.] + +To read multiple files from a tape using DD you must use a non-rewind +device specification, such as "/dev/nrmt0a", and invoke DD once for +each file. Remember to insert "EOF"s at appropriate places in the +descriptor file. + +Although there is an UNIX utility called READ20 that can extract +native files from TOPS-20 DUMPER format tapes (real or virtual), there +is currently none for the inverse operation. In order to transport +native files into the virtual system without a network, pretty much +your only option is to synthesize a virtual tape where the contents +are simple data files. + +WFCONV is a handy utility included with the emulator that will help +prepare such files. It acts as a conversion filter, copying the +standard input to the standard output and converting the data from one +PDP-10 tape format to another. In order to fully understand why this is +needed, you need to understand something about PDP-10 tape formats; for +now, just note the following examples, and see the appendix for details. + +Example 1: transferring an ASCII text file: + + # Make NL -> CRLF and convert into core-dump format + % wfconv -tc > temp.tap + # Find size of file + % ls -l temp.tap + -rw-rw-r-- 1 klh klh10 18641 Apr 1 1994 temp.tap + # Use size to compute number of 512-byte records + # 18641/512 = 36, plus leftover record of 209 + % cat > temp + TF-Format: raw + 0: 512*36 209 EOF EOF ; 36 records plus a short 37th + EOT: + ^D + % + ;; Then in TOPS-20 after mounting the tape "temp": + ;; Note default format is CORE-DUMP, default reclen is 512. + @copY (FROM) mta0: (TO) temp.txt + MTA0: => TEMP.TXT.1 [OK] + @vd temp.txt + + PS: + TEMP.TXT.1;P777700 8 3729(36) 20-Apr-97 01:00:59 OPERATOR + @ + + +Example 2: transferring a binary file of 8-bit bytes (no conversion needed): + + % cp temp.dat temp.tap + % ls -l temp.tap # Find size of file + -rw-rw-r-- 1 klh klh10 18641 Feb 13 1994 temp.tap + % cat > temp + TF-Format: raw + 0: 2560*7 721 EOF EOF + EOT: + ^D + % + ;; Then in TOPS-20 after mounting the tape "temp": + @set taPE rECORD-LENGTH (TO) 2560 + @set taPE fORMAT (TO) iNDUSTRY-COMPATIBLE + @copY (FROM) mta0: (TO) temp.dat, + @@byTE (SIZE) 8 + @@ + MTA0: => TEMP.DAT.1 [OK] + @vd temp.dat + + PS: + TEMP.DAT.1;P777700 10 18641(8) 19-Apr-97 23:57:46 OPERATOR + @ + + +Example 3: building a TOPS-20 installation tape + + If you already have a TOPS-20 system, especially one with local +modifications (which pretty much includes everyone), you may wish to +build your own installation tape. The following illustrates what the +tape directory of the V7.0 TOPS-20 installation tape looks like. Note +it does not have an initial bootstrap on it since MTBOOT is assumed to +be already loaded in the PDP-10 memory. + + ; Tape directory for V7.0 TOPS-20 installation tape + ; Bytes: 22519060, Records: 8704, Files(EOF marks): 7 + TF-Format: raw + 0: 2560*597 EOF ; MONITR.EXE + 1528320: 2560*125 EOF ; EXEC.EXE + 1848320: 2560*8 EOF ; DLUSER.EXE + 1868800: 1270 EOF ; DLUSER data + 1870070: 2560*36 EOF ; DUMPER.EXE + 1962230: 2590*7937 EOF ; DUMPER savesets for , etc. + 22519060: EOF + EOT: + +All of these files must be in core-dump format. You can generate the +tape either by: + (1) creating it on a real TOPS system and then using it directly + or using TAPEDD to copy it into a virtual tape. + (2) copying the necessary files over, converting them into core-dump + format, and concatenating them to form a virtual tape. + +See "klt20.txt" for more details on how such an installation tape is +actually used. + + +Quick lesson in tape formats: + + A PDP-10 word is 36 bits. The 8-bit bytes ("frames" on 9-track +drives) that are now universal for tape don't fit exactly into 36 bits, +which opens a Pandora's Box of packing options. The basic formats +offered at one time or another on Digital systems are as follows: + + WFCONV mode TOPS-20 name + ----------- ------------------ + a ANSI-ASCII + c CORE-DUMP + h HIGH-DENSITY + i INDUSTRY-COMPATIBLE + s SIXBIT + +Only CORE-DUMP and INDUSTRY-COMPATIBLE are universally supported; the +others are features of older or newer formatters. In general, the TOPS-20 +monitor expects CORE-DUMP format so that should be your normal choice. + + Tape_Coredump (5 tape bytes per word) + B0 1 2 3 4 5 6 7 + 8 9 10 11 12 13 14 15 + 16 17 18 19 20 21 22 23 + 24 25 26 27 28 29 30 31 + 0 0 0 0 32 33 34 35 + + Tape_Indust (4 tape bytes per PARTIAL word) + B0 1 2 3 4 5 6 7 + 8 9 10 11 12 13 14 15 + 16 17 18 19 20 21 22 23 + 24 25 26 27 28 29 30 31 ; Bits 32-35 are unused! + + Tape_Sixbit (6 tape bytes per word) + 0 0 B0 1 2 3 4 5 + 0 0 6 7 8 9 10 11 + 0 0 12 13 14 15 16 17 + 0 0 18 19 20 21 22 23 + 0 0 24 25 26 27 28 29 + 0 0 30 31 32 33 34 35 + + Tape_Ascii (5 tape bytes per word) + 0 B0 1 2 3 4 5 6 + 0 7 8 9 10 11 12 13 + 0 14 15 16 17 18 19 20 + 0 21 22 23 24 25 26 27 + 35 28 29 30 31 32 33 34 + + Tape_Hidens (9 tape bytes per 2 words) + B0 1 2 3 4 5 6 7 + 8 9 10 11 12 13 14 15 + 16 17 18 19 20 21 22 23 + 24 25 26 27 28 29 30 31 + 32 33 34 35 B0 1 2 3 + 4 5 6 7 8 9 10 11 + 12 13 14 15 16 17 18 19 + 20 21 22 23 24 25 26 27 + 28 29 30 31 32 33 34 35 + + +WFCONV operation notes +====================== + +Invoking WFCONV without arguments will print out a summary similar to +the following: + +Usage: wfconv -io outfile\n\ + where 'i' and 'o' are chars specifying the input and output formats: + c - Core-dump (std tape format, 4 8-bit, 1 4-bit bytes = 36 bits) + h - High-density (FTP 36-bit image, 9 8-bit bytes = 72 bits) + a,7 - Ansi-Ascii (4 7-bit, 1 8-bit byte = 36 bits) + s,6 - Sixbit (6 6-bit bytes = 36 bits) + u - Unixified (Alan Bawden format, various = 36 bits) + t - Text-Newline (CRLF-NL conversion; 5 7-bit bytes = 35 bits ONLY) + d - Debug (for output only) + Note: EOF on input always causes zero-padding up to a PDP-10 word boundary. + +Typical uses of WFCONV would be as follows: + +1. Examining a PDP-10 text file copied from a tape: + + # Convert from core-dump to ansi-ascii (preserve CRs) + $ wfconv -ca < file.tap > file.asc + or: + # Convert from core-dump to text-newline (convert CRLFs to NLs) + $ wfconv -ct < file.tap > file.txt + +2. Preparing a text file for writing to tape: + + # Convert from ansi-ascii to core-dump (preserve NLs) + $ wfconv -ac < file.asc > file.tap + or: + # Convert from text-newline to core-dump (convert NLs to CRLFs) + $ wfconv -tc < file.txt > file.tap + +3. Preparing a file for tape after transferring it from a real PDP-10 + using FTP 36-bit Image mode: + + # Convert from high-density (FTP Image) to core-dump + $ wfconv -hc < file.ftp > file.tap + + +Note: + + Due to the fact that PDP-10 words are used as the canonical input + conversion target (and output conversion source), input is always + effectively rounded up to a word boundary. + + This can cause the addition of a few trailing zero bytes when converting + from Text-Newline or Ansi-ASCII format, so WFCONV is not a + general-purpose text-to-text conversion program. For that, one can use: + + # To insert CR (must use ^V or equiv to quote the ^M) + $ sed 's/$/\^M/' temp.nls > temp.crlfs + + # To delete CR (must use ^V or equiv to quote the ^M) + $ sed 's/\^M$//' temp.crlfs > temp.nls + diff --git a/src/Makefile.mk b/src/Makefile.mk new file mode 100644 index 0000000..54808ce --- /dev/null +++ b/src/Makefile.mk @@ -0,0 +1,723 @@ +# KLH10 Makefile. +# $Id: Makefile.mk,v 2.5 2001/11/19 12:10:25 klh Exp $ +# +# Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +# All Rights Reserved +# +# This file is part of the KLH10 Distribution. Use, modification, and +# re-distribution is permitted subject to the terms in the file +# named "LICENSE", which contains the full text of the legal notices +# and should always accompany this Distribution. +# +# This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +# +# This notice (including the copyright and warranty disclaimer) +# must be included in all copies or derivations of this software. +# +##################################################################### + +# KLH10 Makefile scheme +# +# /src/ +# Makefile - Top-level makefile for in-src build (not recommended) +# Makefile.mk - All generic rules and definitions +# Mk-.mk - Platform-specific definitions +# +# /bld/ +# Makefile -> ../../src/Mk-.mk +# (or local version thereof) +# [any locally munged .h files] +# +# Each top-level makefile should define at least the following: +# SRC = +# +##################################################################### + + +# Basic default definitions. +# Normally these will be overridden by build-specific make +# invocations, by both or either of: +# (1) concatenation of a platform-specific makefile of the +# form "Mk-.mk" +# (the most recent defs override these defaults) +# (2) command line definitions, which override those from any files. + +# Generic compile/link flags +# Suitable for plain vanilla Unix but normally overridden. +CC = cc +CFLAGS = -c -I. -I$(SRC) +CFLAGS_AUX = +CFLAGS_LINT = +LINKER = $(CC) +LDFLAGS = +LDOUTF = -o +LIBS = + +# Variables specific to this makefile setup +# SRC and MAKE_CENV are normally overridden. +SRC = ../../src +MAKE_CENV = +CENVFLAGS = +CONFFLAGS = +CONFFLAGS_AUX = + +MAKER = make -f $(SRC)/Makefile.mk $(MAKE_CENV) +BUILDMOD = $(CC) $(CFLAGS) $(CFLAGS_AUX) \ + $(CENVFLAGS) $(CONFFLAGS) $(CONFFLAGS_AUX) + + +## Default if no target given to make. +## +default: + @echo 'Intended to be invoked from a bld// directory, look' + @echo 'at bld/*/Makefile for examples.' + +## Default if no target given to bld/ invocation +## +usage: + @echo 'Use "make ", eg "make base-kl"' + @echo 'Normally the target is one of these 3 base configs:' + @echo ' base-kl KL10 version for TOPS (kn10-kl and utils)' + @echo ' base-ks KS10 version for TOPS (kn10-ks and utils)' + @echo ' base-ks-its KS10 version for ITS (kn10-ks and utils)' + @echo 'Or these utilities:' + @echo ' tapedd Tape copy & conversion' + @echo ' vdkfmt Virtual disk copy & conversion' + @echo ' wxtest Test w10_t internals' + @echo ' enaddr Show and manage ether interfaces' + @echo 'Or these actions:' + @echo ' clean Clean binaries from build directory' + @echo ' install Install binaries in $$KLH10_HOME' + +## Help for makefile debugging +## +showdefs: + @echo "Showing target defs:" + @echo "SRC = $(SRC)" + @echo "MAKER = $(MAKER)" + @echo "CFLAGS = $(CFLAGS)" + @echo "CFLAGS_AUX = $(CFLAGS_AUX)" + @echo "CENVFLAGS = $(CENVFLAGS)" + @echo "CONFFLAGS = $(CONFFLAGS)" + @echo "CONFFLAGS_AUX = $(CONFFLAGS_AUX)" + @echo "BUILDMOD = $(BUILDMOD)" + + +# Generally applicable rules + +# It would be nice to use the .c.o inference rule for all the .o files, +# but the DECOSF "make" chokes when sources are in a different directory, +# never even invoking .c.o at all. Their /usr/bin/posix/make loses too +# because its MAKEARGS macro prevents recursive makes from accepting +# args with spaces in them. +# +# Thus, for max portability every module target must have explicit +# build commands, and due to the same DECOSF lossage cannot use $< +# in those commands. Sigh! + +.SUFFIXES: $(SUFFIXES) .i + +.c.o: + $(BUILDMOD) $< + +.c.s: + $(BUILDMOD) -S $< + +.c.i: + $(BUILDMOD) -E $< > $*.i + +# Don't flush these files if interrupted. +# Currently no intermediate source files are generated, so +# this can be empty, but hang on to last binary anyway. +.PRECIOUS: kn10-ks kn10-kl + + +####################################################################### +## +## Define sources constituting the KLH10. I have not bothered +## to derive a full set of dependencies for each module since +## there are too many possible combinations; safest to always +## recompile everything. +## +## Also, note the KS and KL have two independent module lists in order +## to allow controlling the order in which modules are loaded; this +## can help improve locality. + +# Generic header files + +CONFS = cenv.h klh10.h word10.h wfio.h feload.h \ + kn10mac.h kn10def.h kn10pag.h kn10clk.h kn10dev.h kn10ops.h \ + opcods.h opdefs.h osdsup.h \ + dvcty.h dvuba.h dvrh11.h dvlhdh.h dvdz11.h dvch11.h \ + dvrh20.h dvrpxx.h dvtm03.h dvni20.h dvhost.h \ + vmtape.h vdisk.h + +# Modules needed for KL10 version. + +OFILES_KL = klh10.o prmstr.o feload.o wfio.o osdsup.o \ + kn10cpu.o kn10pag.o kn10clk.o opdata.o kn10ops.o \ + inmove.o inhalf.o inblsh.o intest.o \ + infix.o inflt.o inbyte.o injrst.o \ + inexts.o inio.o kn10dev.o \ + dvcty.o dvdte.o \ + vdisk.o dvrpxx.o dvrh20.o \ + vmtape.o dvtm03.o \ + dvni20.o dpsup.o \ + dvhost.o + +# Modules needed for KS10 version. + +OFILES_KS = klh10.o prmstr.o feload.o wfio.o osdsup.o \ + kn10cpu.o kn10pag.o kn10clk.o opdata.o kn10ops.o \ + inmove.o inhalf.o inblsh.o intest.o \ + infix.o inflt.o inbyte.o injrst.o \ + inexts.o inio.o kn10dev.o dvuba.o \ + dvcty.o \ + vdisk.o dvrpxx.o dvrh11.o \ + vmtape.o dvtm03.o \ + dvlhdh.o dvdz11.o dvch11.o \ + dpsup.o \ + dvhost.o + +# Device Processes (DPs) built concurrently with KN10 + +DPROCS_KL = dprpxx dptm03 dpni20 +DPROCS_KS = dprpxx dptm03 +DPROCS_KSITS = dprpxx dptm03 dpimp + + +# Base utility programs, independent of KN10 +# (there are others not included in the base configs) + +BASE_UTILS = wfconv tapedd vdkfmt wxtest +ALL_UTILS = $(BASE_UTILS) udlconv uexbconv enaddr + + +############################################################ +## KLH10 config - helper definitions +## + +# Subflags for fully synchronous time emulation +# These are good for debugging, or on a slow machine. +TSYNCFLAGS = \ + -DKLH10_RTIME_SYNCH=1 \ + -DKLH10_ITIME_SYNCH=1 \ + -DKLH10_QTIME_SYNCH=1 + +# Subflags for fully OS-interrupt-driven time emulation +# These are best for high performance on a fast machine. +TINTFLAGS = \ + -DKLH10_RTIME_OSGET=1 \ + -DKLH10_ITIME_INTRP=1 \ + -DKLH10_QTIME_OSVIRT=1 + +# Subflags for synchronous polling versions of certain device drivers. +# These are good for debugging. +DSYNCFLAGS = \ + -DKLH10_IMPIO_INT=0 \ + -DKLH10_CTYIO_INT=0 + +# Subflags for interrupt-driven versions of certain device drivers. +# These are best for high performance. +DINTFLAGS = \ + -DKLH10_IMPIO_INT=1 \ + -DKLH10_CTYIO_INT=1 + + +#################################################################### +## +## Basic KN10 configurations +## + +kn10-ks: $(OFILES_KS) + $(LINKER) $(LDFLAGS) $(LDOUTF) kn10-ks $(OFILES_KS) $(LIBS) + +kn10-kl: $(OFILES_KL) + $(LINKER) $(LDFLAGS) $(LDOUTF) kn10-kl $(OFILES_KL) $(LIBS) + + +#################################################################### +## Auxiliary action targets + +clean: + @rm -f kn10-ks kn10-kl *.o \ + $(DPROCS_KL) $(DPROCS_KS) $(DPROCS_KSITS) \ + $(ALL_UTILS) + + +# Install. This should really use a shell script instead. +# +install-unix: + @echo "Copying binaries into ${KLH10_HOME}" + @-rm -rf ${KLH10_HOME}/flushed + @-mkdir ${KLH10_HOME}/flushed + @if [ -x ${KLH10_HOME}/kn10-ks ]; then \ + mv ${KLH10_HOME}/kn10-ks ${KLH10_HOME}/flushed; fi + @if [ -x ${KLH10_HOME}/kn10-kl ]; then \ + mv ${KLH10_HOME}/kn10-kl ${KLH10_HOME}/flushed; fi + @if [ -x ${KLH10_HOME}/dprpxx ]; then \ + mv ${KLH10_HOME}/dprpxx ${KLH10_HOME}/flushed; fi + @if [ -x ${KLH10_HOME}/dptm03 ]; then \ + mv ${KLH10_HOME}/dptm03 ${KLH10_HOME}/flushed; fi + @if [ -x ${KLH10_HOME}/dpni20 ]; then \ + mv ${KLH10_HOME}/dpni20 ${KLH10_HOME}/flushed; fi + @if [ -x ${KLH10_HOME}/dpimp ]; then \ + mv ${KLH10_HOME}/dpimp ${KLH10_HOME}/flushed; fi + @if [ -x kn10-ks ]; then cp -p kn10-ks ${KLH10_HOME}/; fi + @if [ -x kn10-kl ]; then cp -p kn10-kl ${KLH10_HOME}/; fi + @if [ -x dprpxx ]; then cp -p dprpxx ${KLH10_HOME}/; fi + @if [ -x dptm03 ]; then cp -p dptm03 ${KLH10_HOME}/; fi + @if [ -x dpni20 ]; then cp -p dpni20 ${KLH10_HOME}/; fi + @if [ -x dpimp ]; then cp -p dpimp ${KLH10_HOME}/; fi + @if [ -x enaddr ]; then cp -p enaddr ${KLH10_HOME}/; fi + @if [ -x tapedd ]; then cp -p tapedd ${KLH10_HOME}/; fi + @if [ -x udlconv ]; then cp -p udlconv ${KLH10_HOME}/; fi + @if [ -x uexbconv ]; then cp -p uexbconv ${KLH10_HOME}/; fi + @if [ -x vdkfmt ]; then cp -p vdkfmt ${KLH10_HOME}/; fi + @if [ -x wfconv ]; then cp -p wfconv ${KLH10_HOME}/; fi + @if [ -x wxtest ]; then cp -p wxtest ${KLH10_HOME}/; fi + @echo "Done!" + +#################################################################### +## Specific KLH10 configurations +## +## Provided as a convenience, not intended to satisfy all +## possible platforms or configurations. + +# Standard setup for KS ITS +# +base-ks-its: + $(MAKER) kn10-ks $(DPROCS_KSITS) $(BASE_UTILS) udlconv \ + "SRC = $(SRC)" \ + "CC = $(CC)" \ + "CFLAGS = $(CFLAGS) $(CFLAGS_AUX)" \ + "LDFLAGS = $(LDFLAGS)" \ + "LIBS = $(LIBS)" \ + "CENVFLAGS = $(CENVFLAGS)" \ + "CONFFLAGS = \ + -DKLH10_CPU_KS=1 \ + -DKLH10_SYS_ITS=1 \ + -DKLH10_EVHS_INT=1 \ + -DKLH10_DEV_DPTM03=1 \ + -DKLH10_DEV_DPRPXX=1 \ + -DKLH10_DEV_DPIMP=1 \ + -DKLH10_SIMP=0 \ + -DKLH10_NET_TUN=SYS_FREEBSD \ + -DKLH10_MEM_SHARED=1 \ + $(TINTFLAGS) \ + $(DINTFLAGS) \ + -DKLH10_APRID_SERIALNO=759 -DKLH10_DEVMAX=12 \ + -DKLH10_CLIENT=\\\"MyITS\\\" \ + $(CONFFLAGS_AUX) \ + -DVMTAPE_ITSDUMP=1 " + + +# Standard setup for KS (TOPS-20, maybe TOPS-10) +# +base-ks: + $(MAKER) kn10-ks $(DPROCS_KS) $(BASE_UTILS) \ + "SRC = $(SRC)" \ + "CC = $(CC)" \ + "CFLAGS = $(CFLAGS) $(CFLAGS_AUX)" \ + "LDFLAGS = $(LDFLAGS)" \ + "LIBS = $(LIBS)" \ + "CENVFLAGS = $(CENVFLAGS)" \ + "CONFFLAGS = \ + -DKLH10_CPU_KS=1 \ + -DKLH10_SYS_T20=1 \ + -DKLH10_EVHS_INT=1 \ + -DKLH10_DEV_DPTM03=1 \ + -DKLH10_DEV_DPRPXX=1 \ + -DKLH10_MEM_SHARED=1 \ + $(TINTFLAGS) \ + $(DINTFLAGS) \ + -DKLH10_APRID_SERIALNO=759 -DKLH10_DEVMAX=12 \ + -DKLH10_CLIENT=\\\"MyKS\\\" \ + $(CONFFLAGS_AUX) " + +# Standard setup for KL (TOPS-10 and TOPS-20) +# +base-kl: + $(MAKER) kn10-kl $(DPROCS_KL) $(BASE_UTILS) uexbconv \ + "SRC = $(SRC)" \ + "CC = $(CC)" \ + "CFLAGS = $(CFLAGS) $(CFLAGS_AUX)" \ + "LDFLAGS = $(LDFLAGS)" \ + "LIBS = $(LIBS)" \ + "CENVFLAGS = $(CENVFLAGS)" \ + "CONFFLAGS = \ + -DKLH10_CPU_KLX=1 \ + -DKLH10_SYS_T20=1 \ + -DKLH10_EVHS_INT=1 \ + -DKLH10_DEV_DPNI20=1 \ + -DKLH10_DEV_DPTM03=1 \ + -DKLH10_DEV_DPRPXX=1 \ + -DKLH10_MEM_SHARED=1 \ + -DKLH10_RTIME_OSGET=1 \ + -DKLH10_ITIME_INTRP=1 \ + -DKLH10_CTYIO_INT=1 \ + -DKLH10_APRID_SERIALNO=1 \ + -DKLH10_CLIENT=\\\"MyKL\\\" \ + $(CONFFLAGS_AUX) " + +#################################################################### +## Lintish versions to see how many compiler warnings we can generate +## +lint-ks-its: + $(MAKER) kn10-ks $(DPROCS_KSITS) $(BASE_UTILS) udlconv \ + "SRC = $(SRC)" \ + "CC = $(CC)" \ + "CFLAGS = $(CFLAGS) $(CFLAGS_AUX) $(CFLAGS_LINT)" \ + "LDFLAGS = $(LDFLAGS)" \ + "LIBS = $(LIBS)" \ + "CENVFLAGS = $(CENVFLAGS)" \ + "CONFFLAGS = $(CONFFLAGS) $(CONFFLAGS_AUX)" + +lint-ks: + $(MAKER) kn10-ks $(DPROCS_KS) $(BASE_UTILS) \ + "SRC = $(SRC)" \ + "CC = $(CC)" \ + "CFLAGS = $(CFLAGS) $(CFLAGS_AUX) $(CFLAGS_LINT)" \ + "LDFLAGS = $(LDFLAGS)" \ + "LIBS = $(LIBS)" \ + "CENVFLAGS = $(CENVFLAGS)" \ + "CONFFLAGS = $(CONFFLAGS) $(CONFFLAGS_AUX)" + +lint-kl: + $(MAKER) kn10-kl $(DPROCS_KL) $(BASE_UTILS) uexbconv \ + "SRC = $(SRC)" \ + "CC = $(CC)" \ + "CFLAGS = $(CFLAGS) $(CFLAGS_AUX) $(CFLAGS_LINT)" \ + "LDFLAGS = $(LDFLAGS)" \ + "LIBS = $(LIBS)" \ + "CENVFLAGS = $(CENVFLAGS)" \ + "CONFFLAGS = $(CONFFLAGS) $(CONFFLAGS_AUX)" + + +#################################################################### +## KLH10 versions for diagnostics and debugging. +## + +# "Port"-friendly KS, for helping port to a new platform. +# Simplest possible configuration: +# No shared memory +# No device subprocs +# No realtime interrupts or clock - synchronous emulation +port-ks: + $(MAKER) kn10-ks $(BASE_UTILS) \ + "SRC = $(SRC)" \ + "CC = $(CC)" \ + "CFLAGS = $(CFLAGS) $(CFLAGS_AUX)" \ + "LDFLAGS = $(LDFLAGS)" \ + "LIBS = $(LIBS)" \ + "CENVFLAGS = $(CENVFLAGS)" \ + "CONFFLAGS = \ + -DKLH10_CPU_KS=1 \ + -DKLH10_SYS_T20=1 \ + -DKLH10_RTIME_SYNCH=1 \ + -DKLH10_APRID_SERIALNO=759 -DKLH10_DEVMAX=12 \ + -DKLH10_CLIENT=\\\"MyKS\\\" \ + $(CONFFLAGS_AUX) " + + +# Build KL0 with KI paging, for running diagnostics. +# Two versions, one synch and one realtime. +# Note: The diags tend to fail miserably when faced with a KLX using KI +# paging, so don't try that. + +# KL0 with KI paging - synchronous, good for debugging with diagnostics. +# NOTE: CFLAGS for this one should be set up for NO optimization!!! +kl0i-sync: + $(MAKER) kn10-kl $(DPROCS_KL) \ + "SRC = $(SRC)" \ + "CC = $(CC)" \ + "CFLAGS = $(CFLAGS) $(CFLAGS_AUX)" \ + "LDFLAGS = $(LDFLAGS)" \ + "LIBS = $(LIBS)" \ + "CENVFLAGS = $(CENVFLAGS)" \ + "CONFFLAGS = \ + -DKLH10_CPU_KL0=1 \ + -DKLH10_SYS_T10=1 \ + -DKLH10_PAG_KI=1 \ + -DKLH10_EVHS_INT=1 \ + -DKLH10_DEV_DPNI20=1 \ + -DKLH10_DEV_DPTM03=1 \ + -DKLH10_DEV_DPRPXX=1 \ + -DKLH10_RTIME_SYNCH=1 \ + -DKLH10_ITIME_SYNCH=1 \ + -DKLH10_CTYIO_INT=0 \ + $(CONFFLAGS_AUX) " + +# KL0 with KI paging - Realtime & optimized, good for timing diagnostics. +# +kl0i-rtmopt: + $(MAKER) kn10-kl $(DPROCS_KL) \ + "SRC = $(SRC)" \ + "CC = $(CC)" \ + "CFLAGS = $(CFLAGS) $(CFLAGS_AUX)" \ + "LDFLAGS = $(LDFLAGS)" \ + "LIBS = $(LIBS)" \ + "CENVFLAGS = $(CENVFLAGS)" \ + "CONFFLAGS = \ + -DKLH10_CPU_KL0=1 \ + -DKLH10_SYS_T10=1 \ + -DKLH10_PAG_KI=1 \ + -DKLH10_EVHS_INT=1 \ + -DKLH10_DEV_DPNI20=1 \ + -DKLH10_DEV_DPTM03=1 \ + -DKLH10_DEV_DPRPXX=1 \ + -DKLH10_RTIME_OSGET=1 \ + -DKLH10_ITIME_INTRP=1 \ + -DKLH10_CTYIO_INT=0 \ + $(CONFFLAGS_AUX) " + + +#################################################################### +## Device Process (DP) programs +## +## These cannot be made individually - they are expected to be +## built as byproducts of building the KLH10, in order to share +## a common set of config parameters. +## + +# --------- RPXX disk drive subprocess +# +dprpxx.o: $(SRC)/dprpxx.c $(SRC)/dprpxx.h $(SRC)/dpsup.h $(SRC)/vdisk.c + $(BUILDMOD) $(SRC)/dprpxx.c + +dprpxx: dprpxx.o dpsup.o + $(LINKER) $(LDFLAGS) $(LDOUTF) dprpxx dprpxx.o dpsup.o $(LIBS) + + +# --------- TM03 tape drive subprocess +# +dptm03.o: $(SRC)/dptm03.c $(SRC)/dptm03.h $(SRC)/dpsup.h $(SRC)/vmtape.c + $(BUILDMOD) $(SRC)/dptm03.c + +OFILES_DPTM03=dptm03.o dpsup.o wfio.o prmstr.o + +dptm03: $(OFILES_DPTM03) + $(LINKER) $(LDFLAGS) $(LDOUTF) dptm03 $(OFILES_DPTM03) $(LIBS) + + +# --------- NI20 Network Interface subprocess (KL only) +# +dpni20.o: $(SRC)/dpni20.c $(SRC)/dpni20.h $(SRC)/dpsup.h + $(BUILDMOD) $(SRC)/dpni20.c + +dpni20: dpni20.o dpsup.o + $(LINKER) $(LDFLAGS) $(LDOUTF) dpni20 dpni20.o dpsup.o $(LIBS) + + +# --------- IMP subprocess (ITS KS only; counterpart for dvlhdh) +# +dpimp.o: $(SRC)/dpimp.c $(SRC)/dpimp.h $(SRC)/dpsup.h + $(BUILDMOD) $(SRC)/dpimp.c + +dpimp: dpimp.o dpsup.o + $(LINKER) $(LDFLAGS) $(LDOUTF) dpimp dpimp.o dpsup.o $(LIBS) + + +#################################################################### +## UTILITIES +## +## These can be built independently, and normally do not require +## any CONFFLAGS. +## + +## TAPEDD - Tape device-to-device copy +## Needs CONFFLAGS just for optional VMTAPE_ITSDUMP. +## +tapedd.o: $(SRC)/tapedd.c $(SRC)/vmtape.c $(SRC)/vmtape.h + $(CC) $(CFLAGS) $(CENVFLAGS) $(CONFFLAGS) $(SRC)/tapedd.c + +tapedd: tapedd.o wfio.o prmstr.o + $(LINKER) $(LDFLAGS) $(LDOUTF) tapedd tapedd.o wfio.o prmstr.o $(LIBS) + + +## VDKFMT - Virtual Disk Format & copy +## +vdkfmt.o: $(SRC)/vdkfmt.c $(SRC)/vdisk.c $(SRC)/vdisk.h + $(CC) $(CFLAGS) $(CENVFLAGS) $(SRC)/vdkfmt.c + +vdkfmt: vdkfmt.o + $(LINKER) $(LDFLAGS) $(LDOUTF) vdkfmt vdkfmt.o $(LIBS) + + +## WXTEST - word10.h tester +## +wxtest.o: $(SRC)/wxtest.c $(SRC)/word10.h + $(CC) $(CFLAGS) $(CENVFLAGS) $(SRC)/wxtest.c + +wxtest: wxtest.o + $(LINKER) $(LDFLAGS) $(LDOUTF) wxtest wxtest.o $(LIBS) + + +## WFCONV - Word-File Conversion +## +wfconv.o: $(SRC)/wfconv.c $(SRC)/wfio.c $(SRC)/wfio.h $(SRC)/word10.h + $(CC) $(CFLAGS) $(CENVFLAGS) $(SRC)/wfconv.c + +wfconv: wfconv.o + $(LINKER) $(LDFLAGS) $(LDOUTF) wfconv wfconv.o $(LIBS) + + +## UDLCONV - DIR.LIST Conversion (of ITS interest only) +## +udlconv.o: $(SRC)/udlconv.c + $(CC) $(CFLAGS) $(CENVFLAGS) $(SRC)/udlconv.c + +udlconv: udlconv.o + $(LINKER) $(LDFLAGS) $(LDOUTF) udlconv udlconv.o $(LIBS) + + +## UEXBCONV - Convert .EXB file into .SAV (of KL interest only) +## +uexbconv.o: $(SRC)/uexbconv.c $(SRC)/wfio.c $(SRC)/wfio.h $(SRC)/word10.h + $(CC) $(CFLAGS) $(CENVFLAGS) $(SRC)/uexbconv.c + +uexbconv: uexbconv.o + $(LINKER) $(LDFLAGS) $(LDOUTF) uexbconv uexbconv.o $(LIBS) + + +## ENADDR - Ethernet interface test & manipulation +## May require CONFFLAGS to force a particular osdnet config. +enaddr.o: $(SRC)/enaddr.c $(SRC)/osdnet.h $(SRC)/osdnet.c + $(CC) $(CFLAGS) $(CENVFLAGS) $(CONFFLAGS) $(SRC)/enaddr.c + +enaddr: enaddr.o + $(LINKER) $(LDFLAGS) $(LDOUTF) enaddr enaddr.o $(LIBS) + + +#################################################################### +## KN10 modules. In order to build into a directory other than the +## one the sources are located, must specify the location of +## each and every source file. Ugh. Some but not all dependencies +## are included here. +## +## Sorted alphabetically. +## + +dpsup.o: $(SRC)/dpsup.c $(SRC)/dpsup.h + $(BUILDMOD) $(SRC)/dpsup.c + +dvch11.o: $(SRC)/dvch11.c $(SRC)/dvch11.h + $(BUILDMOD) $(SRC)/dvch11.c + +dvcty.o: $(SRC)/dvcty.c $(SRC)/dvcty.h + $(BUILDMOD) $(SRC)/dvcty.c + +dvdte.o: $(SRC)/dvdte.c $(SRC)/dvdte.h + $(BUILDMOD) $(SRC)/dvdte.c + +dvdz11.o: $(SRC)/dvdz11.c $(SRC)/dvdz11.h + $(BUILDMOD) $(SRC)/dvdz11.c + +dvhost.o: $(SRC)/dvhost.c $(SRC)/dvhost.h + $(BUILDMOD) $(SRC)/dvhost.c + +dvlhdh.o: $(SRC)/dvlhdh.c $(SRC)/dvlhdh.h + $(BUILDMOD) $(SRC)/dvlhdh.c + +dvni20.o: $(SRC)/dvni20.c $(SRC)/dvni20.h + $(BUILDMOD) $(SRC)/dvni20.c + +dvrh11.o: $(SRC)/dvrh11.c $(SRC)/dvrh11.h + $(BUILDMOD) $(SRC)/dvrh11.c + +dvrh20.o: $(SRC)/dvrh20.c $(SRC)/dvrh20.h + $(BUILDMOD) $(SRC)/dvrh20.c + +dvrpxx.o: $(SRC)/dvrpxx.c $(SRC)/dvrpxx.h + $(BUILDMOD) $(SRC)/dvrpxx.c + +dvtm03.o: $(SRC)/dvtm03.c $(SRC)/dvtm03.h + $(BUILDMOD) $(SRC)/dvtm03.c + +dvuba.o: $(SRC)/dvuba.c $(SRC)/dvuba.h + $(BUILDMOD) $(SRC)/dvuba.c + +feload.o: $(SRC)/feload.c $(SRC)/feload.h + $(BUILDMOD) $(SRC)/feload.c + +inblsh.o: $(SRC)/inblsh.c + $(BUILDMOD) $(SRC)/inblsh.c + +inbyte.o: $(SRC)/inbyte.c + $(BUILDMOD) $(SRC)/inbyte.c + +inexts.o: $(SRC)/inexts.c + $(BUILDMOD) $(SRC)/inexts.c + +infix.o: $(SRC)/infix.c + $(BUILDMOD) $(SRC)/infix.c + +inflt.o: $(SRC)/inflt.c + $(BUILDMOD) $(SRC)/inflt.c + +inhalf.o: $(SRC)/inhalf.c + $(BUILDMOD) $(SRC)/inhalf.c + +inio.o: $(SRC)/inio.c + $(BUILDMOD) $(SRC)/inio.c + +injrst.o: $(SRC)/injrst.c + $(BUILDMOD) $(SRC)/injrst.c + +inmove.o: $(SRC)/inmove.c + $(BUILDMOD) $(SRC)/inmove.c + +intest.o: $(SRC)/intest.c + $(BUILDMOD) $(SRC)/intest.c + +kn10clk.o: $(SRC)/kn10clk.c $(SRC)/kn10clk.h + $(BUILDMOD) $(SRC)/kn10clk.c + +kn10cpu.o: $(SRC)/kn10cpu.c $(SRC)/klh10.h $(SRC)/klh10s.h $(SRC)/klh10.c + $(BUILDMOD) $(SRC)/kn10cpu.c + +kn10dev.o: $(SRC)/kn10dev.c $(SRC)/kn10dev.h + $(BUILDMOD) $(SRC)/kn10dev.c + +kn10ops.o: $(SRC)/kn10ops.c $(SRC)/kn10ops.h + $(BUILDMOD) $(SRC)/kn10ops.c + +kn10pag.o: $(SRC)/kn10pag.c $(SRC)/kn10pag.h + $(BUILDMOD) $(SRC)/kn10pag.c + +klh10.o: $(SRC)/klh10.c $(SRC)/klh10.h $(SRC)/klh10s.h + $(BUILDMOD) $(SRC)/klh10.c + +opdata.o: $(SRC)/opdata.c $(SRC)/kn10def.h $(SRC)/opcods.h + $(BUILDMOD) $(SRC)/opdata.c + +osdsup.o: $(SRC)/osdsup.c $(SRC)/osdsup.h + $(BUILDMOD) $(SRC)/osdsup.c + +prmstr.o: $(SRC)/prmstr.c $(SRC)/prmstr.h + $(BUILDMOD) $(SRC)/prmstr.c + +vdisk.o: $(SRC)/vdisk.c $(SRC)/vdisk.h + $(BUILDMOD) $(SRC)/vdisk.c + +vmtape.o: $(SRC)/vmtape.c $(SRC)/vmtape.h + $(BUILDMOD) $(SRC)/vmtape.c + +wfio.o: $(SRC)/wfio.c $(SRC)/wfio.h + $(BUILDMOD) $(SRC)/wfio.c + + +#################################################################### +## OLD STUFF +## Misc crocks kept around just in case. +## + +optest: optest.o + $(LINKER) $(LDFLAGS) -o optest optest.o $(LIBS) + + +# This is only for munching Alan's ITS DUMP tape info format +dlmunch: dlmunch.o lread.o + $(LINKER) -o dlmunch dlmunch.o lread.o + +#################################################################### diff --git a/src/Mk-fbx86.mk b/src/Mk-fbx86.mk new file mode 100644 index 0000000..dd9114f --- /dev/null +++ b/src/Mk-fbx86.mk @@ -0,0 +1,47 @@ +# KLH10 Makefile for FreeBSD on i386 +# $Id: Mk-fbx86.mk,v 2.3 2001/11/10 21:28:59 klh Exp $ +# +# Copyright © 2001 Kenneth L. Harrenstien +# All Rights Reserved +# +# This file is part of the KLH10 Distribution. Use, modification, and +# re-distribution is permitted subject to the terms in the file +# named "LICENSE", which contains the full text of the legal notices +# and should always accompany this Distribution. +# +# This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +# +# This notice (including the copyright and warranty disclaimer) +# must be included in all copies or derivations of this software. +# +##################################################################### + +# Local config setup, for BSD "make"! +# Recursively invokes make with right params for local platform. + +# Build definitions +SRC = ../../src +CFLAGS = -c -g3 -O -I. -I$(SRC) +CFLAGS_LINT = -ansi -pedantic -Wall -Wshadow \ + -Wstrict-prototypes -Wmissing-prototypes \ + -Wmissing-declarations -Wredundant-decls + +# Source definitions +CENVFLAGS = -DCENV_CPU_I386=1 -DCENV_SYS_FREEBSD=1 + +# Any target with no customized rule here is simply passed on to the +# standard Makefile. If no target is specified, "usage" is passed on +# to generate a helpful printout. + +usage: + @make -f $(SRC)/Makefile.mk usage + +install: + @make -f $(SRC)/Makefile.mk install-unix + +$(.TARGETS): + @make -f $(SRC)/Makefile.mk $@ \ + "SRC=$(SRC)" \ + "CFLAGS=$(CFLAGS)" \ + "CFLAGS_LINT=$(CFLAGS_LINT)" \ + "CENVFLAGS=$(CENVFLAGS)" diff --git a/src/Mk-lnx86.mk b/src/Mk-lnx86.mk new file mode 100644 index 0000000..ce1eddb --- /dev/null +++ b/src/Mk-lnx86.mk @@ -0,0 +1,44 @@ +# KLH10 Makefile for Linux on i386 +# $Id: Mk-lnx86.mk,v 2.3 2001/11/10 21:28:59 klh Exp $ +# +# Copyright © 2001 Kenneth L. Harrenstien +# All Rights Reserved +# +# This file is part of the KLH10 Distribution. Use, modification, and +# re-distribution is permitted subject to the terms in the file +# named "LICENSE", which contains the full text of the legal notices +# and should always accompany this Distribution. +# +# This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +# +# This notice (including the copyright and warranty disclaimer) +# must be included in all copies or derivations of this software. +# +##################################################################### + +# Local config setup, for GNU "make"! +# Recursively invokes make with right params for local platform. + +# Build definitions +SRC = ../../src +CFLAGS = -c -g3 -O -I. -I$(SRC) +CFLAGS_LINT = -ansi -pedantic -Wall -Wshadow \ + -Wstrict-prototypes -Wmissing-prototypes \ + -Wmissing-declarations -Wredundant-decls + +# Source definitions +CENVFLAGS = -DCENV_CPU_I386=1 -DCENV_SYS_LINUX=1 + +# Any target with no customized rule here is simply passed on to the +# standard Makefile. If no target is specified, "usage" is passed on +# to generate a helpful printout. + +usage .DEFAULT: + @make -f $(SRC)/Makefile.mk $@ \ + "SRC=$(SRC)" \ + "CFLAGS=$(CFLAGS)" \ + "CFLAGS_LINT=$(CFLAGS_LINT)" \ + "CENVFLAGS=$(CENVFLAGS)" + +install: + make -f $(SRC)/Makefile.mk install-unix diff --git a/src/Mk-lnxarm.mk b/src/Mk-lnxarm.mk new file mode 100644 index 0000000..f728707 --- /dev/null +++ b/src/Mk-lnxarm.mk @@ -0,0 +1,44 @@ +# KLH10 Makefile for Linux on ARM +# $Id: Mk-lnxarm.mk,v 2.3 2001/11/10 21:28:59 klh Exp $ +# +# Copyright © 2001 Kenneth L. Harrenstien +# All Rights Reserved +# +# This file is part of the KLH10 Distribution. Use, modification, and +# re-distribution is permitted subject to the terms in the file +# named "LICENSE", which contains the full text of the legal notices +# and should always accompany this Distribution. +# +# This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +# +# This notice (including the copyright and warranty disclaimer) +# must be included in all copies or derivations of this software. +# +##################################################################### + +# Local config setup, for GNU "make"! +# Recursively invokes make with right params for local platform. + +# Build definitions +SRC = ../../src +CFLAGS = -c -g3 -O -I. -I$(SRC) +CFLAGS_LINT = -ansi -pedantic -Wall -Wshadow \ + -Wstrict-prototypes -Wmissing-prototypes \ + -Wmissing-declarations -Wredundant-decls + +# Source definitions +CENVFLAGS = -DCENV_CPU_ARM=1 -DCENV_SYS_LINUX=1 + +# Any target with no customized rule here is simply passed on to the +# standard Makefile. If no target is specified, "usage" is passed on +# to generate a helpful printout. + +usage .DEFAULT: + @make -f $(SRC)/Makefile.mk $@ \ + "SRC=$(SRC)" \ + "CFLAGS=$(CFLAGS)" \ + "CFLAGS_LINT=$(CFLAGS_LINT)" \ + "CENVFLAGS=$(CENVFLAGS)" + +install: + make -f $(SRC)/Makefile.mk install-unix diff --git a/src/Mk-lnxppc.mk b/src/Mk-lnxppc.mk new file mode 100644 index 0000000..5ee0568 --- /dev/null +++ b/src/Mk-lnxppc.mk @@ -0,0 +1,44 @@ +# KLH10 Makefile for Linux on PowerPC +# $Id: Mk-lnxppc.mk,v 2.3 2001/11/10 21:28:59 klh Exp $ +# +# Copyright © 2001 Kenneth L. Harrenstien +# All Rights Reserved +# +# This file is part of the KLH10 Distribution. Use, modification, and +# re-distribution is permitted subject to the terms in the file +# named "LICENSE", which contains the full text of the legal notices +# and should always accompany this Distribution. +# +# This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +# +# This notice (including the copyright and warranty disclaimer) +# must be included in all copies or derivations of this software. +# +##################################################################### + +# Local config setup, for GNU "make"! +# Recursively invokes make with right params for local platform. + +# Build definitions +SRC = ../../src +CFLAGS = -c -g3 -O -I. -I$(SRC) +CFLAGS_LINT = -ansi -pedantic -Wall -Wshadow \ + -Wstrict-prototypes -Wmissing-prototypes \ + -Wmissing-declarations -Wredundant-decls + +# Source definitions +CENVFLAGS = -DCENV_CPU_PPC=1 -DCENV_SYS_LINUX=1 -DCENV_CPUF_BIGEND=1 + +# Any target with no customized rule here is simply passed on to the +# standard Makefile. If no target is specified, "usage" is passed on +# to generate a helpful printout. + +usage .DEFAULT: + @make -f $(SRC)/Makefile.mk $@ \ + "SRC=$(SRC)" \ + "CFLAGS=$(CFLAGS)" \ + "CFLAGS_LINT=$(CFLAGS_LINT)" \ + "CENVFLAGS=$(CENVFLAGS)" + +install: + make -f $(SRC)/Makefile.mk install-unix diff --git a/src/Mk-nbaxp.mk b/src/Mk-nbaxp.mk new file mode 100644 index 0000000..bdcfa5b --- /dev/null +++ b/src/Mk-nbaxp.mk @@ -0,0 +1,48 @@ +# KLH10 Makefile for NetBSD on Alpha +# $Id: Mk-nbaxp.mk,v 2.3 2001/11/10 21:28:59 klh Exp $ +# +# Copyright © 2001 Kenneth L. Harrenstien +# All Rights Reserved +# +# This file is part of the KLH10 Distribution. Use, modification, and +# re-distribution is permitted subject to the terms in the file +# named "LICENSE", which contains the full text of the legal notices +# and should always accompany this Distribution. +# +# This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +# +# This notice (including the copyright and warranty disclaimer) +# must be included in all copies or derivations of this software. +# +##################################################################### + +# Local config setup, for BSD "make"! +# Recursively invokes make with right params for local platform. + +# Build definitions +SRC = ../../src +LIBS = +CFLAGS = -c -g3 -O +CFLAGS_LINT = -ansi -pedantic -Wall -Wshadow \ + -Wstrict-prototypes -Wmissing-prototypes \ + -Wmissing-declarations -Wredundant-decls + +# Source definitions +CENVFLAGS = -DCENV_CPU_ALPHA=1 -DCENV_SYS_NETBSD=1 + +# Any target with no customized rule here is simply passed on to the +# standard Makefile. If no target is specified, "usage" is passed on +# to generate a helpful printout. + +usage: + @make -f $(SRC)/Makefile.mk usage + +install: + @make -f $(SRC)/Makefile.mk install-unix + +$(.TARGETS): + @make -f $(SRC)/Makefile.mk $@ \ + "SRC=$(SRC)" \ + "CFLAGS=$(CFLAGS)" \ + "CFLAGS_LINT=$(CFLAGS_LINT)" \ + "CENVFLAGS=$(CENVFLAGS)" diff --git a/src/Mk-nbx86.mk b/src/Mk-nbx86.mk new file mode 100644 index 0000000..b1bed02 --- /dev/null +++ b/src/Mk-nbx86.mk @@ -0,0 +1,49 @@ +# KLH10 Makefile for NetBSD on i386 +# $Id: Mk-nbx86.mk,v 2.3 2001/11/10 21:28:59 klh Exp $ +# +# Copyright © 2001 Kenneth L. Harrenstien +# All Rights Reserved +# +# This file is part of the KLH10 Distribution. Use, modification, and +# re-distribution is permitted subject to the terms in the file +# named "LICENSE", which contains the full text of the legal notices +# and should always accompany this Distribution. +# +# This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +# +# This notice (including the copyright and warranty disclaimer) +# must be included in all copies or derivations of this software. +# +##################################################################### + +# Local config setup, for BSD "make"! +# Recursively invokes make with right params for local platform. + +# Build definitions +# XXX: Is -lcompat still needed? +SRC = ../../src +LIBS = -lcompat +CFLAGS = -c -g3 -O +CFLAGS_LINT = -ansi -pedantic -Wall -Wshadow \ + -Wstrict-prototypes -Wmissing-prototypes \ + -Wmissing-declarations -Wredundant-decls + +# Source definitions +CENVFLAGS = -DCENV_CPU_I386=1 -DCENV_SYS_NETBSD=1 + +# Any target with no customized rule here is simply passed on to the +# standard Makefile. If no target is specified, "usage" is passed on +# to generate a helpful printout. + +usage: + @make -f $(SRC)/Makefile.mk usage + +install: + @make -f $(SRC)/Makefile.mk install-unix + +$(.TARGETS): + @make -f $(SRC)/Makefile.mk $@ \ + "SRC=$(SRC)" \ + "CFLAGS=$(CFLAGS)" \ + "CFLAGS_LINT=$(CFLAGS_LINT)" \ + "CENVFLAGS=$(CENVFLAGS)" diff --git a/src/Mk-nxt.mk b/src/Mk-nxt.mk new file mode 100644 index 0000000..cccf892 --- /dev/null +++ b/src/Mk-nxt.mk @@ -0,0 +1,48 @@ +# KLH10 Makefile for NeXT on M68x +# $Id: Mk-nxt.mk,v 2.4 2001/11/10 21:28:59 klh Exp $ +# +# Copyright © 2001 Kenneth L. Harrenstien +# All Rights Reserved +# +# This file is part of the KLH10 Distribution. Use, modification, and +# re-distribution is permitted subject to the terms in the file +# named "LICENSE", which contains the full text of the legal notices +# and should always accompany this Distribution. +# +# This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +# +# This notice (including the copyright and warranty disclaimer) +# must be included in all copies or derivations of this software. +# +##################################################################### + +##### +# WARNING: This platform is no longer supported! Its makefile is +# retained only as a guide in case anyone wants to re-port it. +##### + +# Local config setup. +# Recursively invokes make with right params for local platform. + +# Build definitions +CC=gcc +CFLAGS = -c -finline-functions -fomit-frame-pointer -O -O2 -pipe +SRC = ../../src +CFLAGS_LINT = + +# Source definitions +CENVFLAGS = -DCENV_CPU_M68=1 -DCENV_SYS_NEXT=1 + +BASELIST = ks-t20 + +default: + @echo "Must specify a target, one of \"$(BASELIST)\"" + +$(BASELIST): + make -f $(SRC)/Makefile.mk kn10-ks wfconv tapedd vdkfmt + "SRC=$(SRC)" "CENVFLAGS=$(CENVFLAGS)" + "CONFFLAGS = + -DWORD10_USEGCCSPARC=1 \ + -DKLH10_CPU_KS=1 \ + -DKLH10_SYS_T20=1 \ + $(TSYNCFLAGS) -DKLH10_CTYIO_INT=0" diff --git a/src/Mk-osfaxp.mk b/src/Mk-osfaxp.mk new file mode 100644 index 0000000..28ee2b7 --- /dev/null +++ b/src/Mk-osfaxp.mk @@ -0,0 +1,51 @@ +# KLH10 Makefile for OSF/1 (DU, Tru64) on Alpha +# $Id: Mk-osfaxp.mk,v 2.3 2001/11/10 21:28:59 klh Exp $ +# +# Copyright © 2001 Kenneth L. Harrenstien +# All Rights Reserved +# +# This file is part of the KLH10 Distribution. Use, modification, and +# re-distribution is permitted subject to the terms in the file +# named "LICENSE", which contains the full text of the legal notices +# and should always accompany this Distribution. +# +# This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +# +# This notice (including the copyright and warranty disclaimer) +# must be included in all copies or derivations of this software. +# +##################################################################### + +# Local config setup, for OSF1/DU/Tru64 "make"! +# Recursively invokes make with right params for local platform. + +# Build definitions +# librt.a is necessary in order to get memlk (mlockall). +# May also want -non_shared in LDFLAGS to avoid OSF version problems. +SRC = ../../src +CFLAGS = -c -g3 -O -std1 -I. -I$(SRC) +CFLAGS_LINT = +LDFLAGS = +LIBS = -lrt + +# Source definitions +CENVFLAGS = -DCENV_CPU_ALPHA=1 -DCENV_SYS_DECOSF=1 + +# Targets + +# Any target with no customized rule here is simply passed on to the +# standard Makefile. If no target is specified, "usage" is passed on +# to generate a helpful printout. + +usage .DEFAULT: + @make -f $(SRC)/Makefile.mk $@ \ + "SRC=$(SRC)" \ + "CFLAGS=$(CFLAGS)" \ + "CFLAGS_LINT=$(CFLAGS_LINT)" \ + "CENVFLAGS=$(CENVFLAGS)" \ + "LDFLAGS=$(LDFLAGS)" \ + "LIBS=$(LIBS)" + +install: + make -f $(SRC)/Makefile.mk install-unix + diff --git a/src/Mk-solsparc-cc.mk b/src/Mk-solsparc-cc.mk new file mode 100644 index 0000000..ae264d1 --- /dev/null +++ b/src/Mk-solsparc-cc.mk @@ -0,0 +1,57 @@ +# KLH10 Makefile for Solaris on SUN Sparc (using SUN's cc) +# $Id: Mk-solsparc-cc.mk,v 2.1 2001/11/19 10:12:27 klh Exp $ +# +# Copyright © 2001 Kenneth L. Harrenstien +# All Rights Reserved +# +# This file is part of the KLH10 Distribution. Use, modification, and +# re-distribution is permitted subject to the terms in the file +# named "LICENSE", which contains the full text of the legal notices +# and should always accompany this Distribution. +# +# This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +# +# This notice (including the copyright and warranty disclaimer) +# must be included in all copies or derivations of this software. +# +##################################################################### + +# Local config setup, for SUN's make & compiler. +# Recursively invokes make with right params for local platform. + +# WARNING! DO NOT USE THIS for SUN C 4.2 and possibly others. + +# Note: these simple compile flags are known to work for Solaris 5 and 8 +# (SunOS 5.5.1 and 5.8). Using "-fast" fails on 5.8, and "-lrt" doesn't +# exist on 5.5.1. + +# Build definitions +# These LIBS are needed only for things using osdnet.c. +CC = /opt/SUNWspro/bin/cc +CFLAGS = -c -g -O +LIBS = -lsocket -lnsl +CONFFLAGS_AUX=-DWORD10_USEHUN=1 + +# Source definitions +SRC = ../../src +CENVFLAGS = -DCENV_CPU_SPARC=1 -DCENV_SYS_SOLARIS=1 + +# Targets + +# Any target with no customized rule here is simply passed on to the +# standard Makefile. If no target is specified, "usage" is passed on +# to generate a helpful printout. + +usage .DEFAULT: + @make -f $(SRC)/Makefile.mk $@ \ + "CC=$(CC)" \ + "SRC=$(SRC)" \ + "CFLAGS=$(CFLAGS)" \ + "CFLAGS_LINT=$(CFLAGS_LINT)" \ + "CENVFLAGS=$(CENVFLAGS)" \ + "CONFFLAGS_AUX=$(CONFFLAGS_AUX)" \ + "LDFLAGS=$(LDFLAGS)" \ + "LIBS=$(LIBS)" + +install: + make -f $(SRC)/Makefile.mk install-unix diff --git a/src/Mk-solsparc.mk b/src/Mk-solsparc.mk new file mode 100644 index 0000000..fbc2f27 --- /dev/null +++ b/src/Mk-solsparc.mk @@ -0,0 +1,58 @@ +# KLH10 Makefile for Solaris on SUN Sparc +# $Id: Mk-solsparc.mk,v 2.4 2001/11/19 10:16:02 klh Exp $ +# +# Copyright © 2001 Kenneth L. Harrenstien +# All Rights Reserved +# +# This file is part of the KLH10 Distribution. Use, modification, and +# re-distribution is permitted subject to the terms in the file +# named "LICENSE", which contains the full text of the legal notices +# and should always accompany this Distribution. +# +# This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +# +# This notice (including the copyright and warranty disclaimer) +# must be included in all copies or derivations of this software. +# +##################################################################### + +# Local config setup, for GNU "make"! +# Recursively invokes make with right params for local platform. + +# Note: this makefile forces the use of GCC; SUN C 4.2 proved to have +# bugs. For a makefile that does use the SUN compiler, use +# Mk-solsparc-cc.mk instead. + +# Build definitions +# These LIBS are needed only for things using osdnet.c. +CC=gcc +CFLAGS = -c -g -O2 +LIBS = -lsocket -lnsl +CFLAGS_LINT = -ansi -pedantic -Wall -Wshadow \ + -Wstrict-prototypes -Wmissing-prototypes \ + -Wmissing-declarations -Wredundant-decls +CONFFLAGS_AUX= -DWORD10_USEGCCSPARC=1 + +# Source definitions +SRC = ../../src +CENVFLAGS = -DCENV_CPU_SPARC=1 -DCENV_SYS_SOLARIS=1 + +# Targets + +# Any target with no customized rule here is simply passed on to the +# standard Makefile. If no target is specified, "usage" is passed on +# to generate a helpful printout. + +usage .DEFAULT: + @make -f $(SRC)/Makefile.mk $@ \ + "CC=$(CC)" \ + "SRC=$(SRC)" \ + "CFLAGS=$(CFLAGS)" \ + "CFLAGS_LINT=$(CFLAGS_LINT)" \ + "CENVFLAGS=$(CENVFLAGS)" \ + "CONFFLAGS_AUX=$(CONFFLAGS_AUX)" \ + "LDFLAGS=$(LDFLAGS)" \ + "LIBS=$(LIBS)" + +install: + make -f $(SRC)/Makefile.mk install-unix diff --git a/src/cenv.h b/src/cenv.h new file mode 100644 index 0000000..56cc962 --- /dev/null +++ b/src/cenv.h @@ -0,0 +1,228 @@ +/* CENV.H - General C Environment Definitions +*/ +/* $Id: cenv.h,v 2.4 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: cenv.h,v $ + * Revision 2.4 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* C environment config definitions, used by various KLH programs. +** +** These specify the TARGET platform, usually but not necessarily the one +** being compiled on. +** +** CENV_CPU_x = target CPU architecture +** CENV_SYS_x = target OS +** CENV_SYSF_x = target OS features +** +** Note that the CPU and SYS are expected to be explicitly specified by +** a command line definition. If no setting is detected this code tries +** a few simple checks, but it is not an error if nothing is set; the +** includer may have portable defaults. +*/ + +#ifndef CENV_INCLUDED +#define CENV_INCLUDED 1 + +#ifdef RCSID + RCSID(cenv_h,"$Id: cenv.h,v 2.4 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Machine architecture - alpha order */ + +#ifndef CENV_CPU_ALPHA /* DEC Alpha AXP series */ +# define CENV_CPU_ALPHA 0 +#endif +#ifndef CENV_CPU_ARM /* DEC/Intel ARM series */ +# define CENV_CPU_ARM 0 +#endif +#ifndef CENV_CPU_I386 /* Intel 386 and up series */ +# define CENV_CPU_I386 0 +#endif +#ifndef CENV_CPU_M68 /* Motorola MC680x0 series */ +# define CENV_CPU_M68 0 +#endif +#ifndef CENV_CPU_PDP10 /* DEC PDP10 series */ +# define CENV_CPU_PDP10 0 +#endif +#ifndef CENV_CPU_PPC /* IBM/Motorola PowerPC series */ +# define CENV_CPU_PPC 0 +#endif +#ifndef CENV_CPU_SPARC /* SUN SPARC series */ +# define CENV_CPU_SPARC 0 +#endif + +/* If none of the above were set, try a few semi-standard checks, + * but don't complain if nothing's found. + */ +#if !(CENV_CPU_M68|CENV_CPU_SPARC|CENV_CPU_PDP10|CENV_CPU_I386 \ + |CENV_CPU_ALPHA|CENV_CPU_PPC) +# if defined(__alpha) || defined(__alpha__) +# undef CENV_CPU_ALPHA +# define CENV_CPU_ALPHA 1 +# elif defined(__arm) || defined(__arm__) +# undef CENV_CPU_ARM +# define CENV_CPU_ARM 1 +# elif defined(__i386) || defined(__i386__) +# undef CENV_CPU_I386 +# define CENV_CPU_I386 1 +# elif defined(__ppc) || defined(__ppc__) +# undef CENV_CPU_PPC +# define CENV_CPU_PPC 1 +# elif defined(__sparc) || defined(__sparc__) +# undef CENV_CPU_SPARC +# define CENV_CPU_SPARC 1 +# elif defined(__COMPILER_KCC__) +# undef CENV_CPU_PDP10 /* Not quite right, but close enough */ +# define CENV_CPU_PDP10 1 +# endif +#endif + +/* Specific CPU Feature defs + This only has features of interest for KLH10 software. + Note: endian-ness cannot be assumed if CPU is unknown. + Note: PowerPC is inherently big-endian, but can support little-endian + memory addressing! Platforms so far (Linux & MacOS) use + big-endian model, but NT/W2K may require little-endian. + */ +#ifndef CENV_CPUF_BIGEND /* True if big-endian */ +# define CENV_CPUF_BIGEND (CENV_CPU_SPARC|CENV_CPU_M68|CENV_CPU_PDP10 \ + |CENV_CPU_PPC) +#endif +#ifndef CENV_CPUF_LILEND /* True if little-endian */ +# define CENV_CPUF_LILEND (CENV_CPU_I386|CENV_CPU_ALPHA|CENV_CPU_ARM) +#endif + + +/* Operating System - alpha order */ + +#ifndef CENV_SYS_BSDI /* 386 BSDI */ +# define CENV_SYS_BSDI 0 +#endif +#ifndef CENV_SYS_DECOSF /* DEC OSF/1 (Digital Unix, Tru64) */ +# define CENV_SYS_DECOSF 0 +#endif +#ifndef CENV_SYS_FREEBSD /* FreeBSD */ +# define CENV_SYS_FREEBSD 0 +#endif +#ifndef CENV_SYS_LINUX /* Linux */ +# define CENV_SYS_LINUX 0 +#endif +#ifndef CENV_SYS_MAC /* Apple Mac (classic, pre-X) */ +# define CENV_SYS_MAC 0 +#endif +#ifndef CENV_SYS_NETBSD /* NetBSD */ +# define CENV_SYS_NETBSD 0 +#endif +#ifndef CENV_SYS_NEXT /* NeXT */ +# define CENV_SYS_NEXT 0 +#endif +#ifndef CENV_SYS_OPENBSD /* OpenBSD */ +# define CENV_SYS_OPENBSD 0 +#endif +#ifndef CENV_SYS_SOLARIS /* SunOS 5.x */ +# define CENV_SYS_SOLARIS 0 +#endif +#ifndef CENV_SYS_SUN /* SunOS 4.x */ +# define CENV_SYS_SUN 0 +#endif +#ifndef CENV_SYS_T20 /* DEC TOPS-20 */ +# define CENV_SYS_T20 0 +#endif +#ifndef CENV_SYS_V7 /* Basic vanilla Unix */ +# define CENV_SYS_V7 0 +#endif +#ifndef CENV_SYS_W2K /* MS W2K */ +# define CENV_SYS_W2K 0 +#endif + +/* If none of the above were set, try a few semi-standard checks, + * but don't complain if nothing's found. + */ +#if !(CENV_SYS_V7|CENV_SYS_SUN|CENV_SYS_SOLARIS|CENV_SYS_NEXT|CENV_SYS_MAC \ + |CENV_SYS_BSDI|CENV_SYS_NETBSD|CENV_SYS_FREEBSD|CENV_SYS_OPENBSD \ + |CENV_SYS_DECOSF|CENV_SYS_LINUX|CENV_SYS_W2K) +# if defined(__osf__) && defined(__digital__) +# undef CENV_SYS_DECOSF +# define CENV_SYS_DECOSF 1 +# elif defined(__FreeBSD__) +# undef CENV_SYS_FREEBSD +# define CENV_SYS_FREEBSD 1 +# elif defined(__linux__) +# undef CENV_SYS_LINUX +# define CENV_SYS_LINUX 1 +# elif defined(__APPLE__) +# undef CENV_SYS_MAC +# define CENV_SYS_MAC 1 +# elif defined(__NetBSD__) +# undef CENV_SYS_NETBSD +# define CENV_SYS_NETBSD 1 +# elif defined(__OpenBSD__) +# undef CENV_SYS_OPENBSD +# define CENV_SYS_OPENBSD 1 +# elif defined(__sun) && defined(__SVR4) +# undef CENV_SYS_SOLARIS +# define CENV_SYS_SOLARIS 1 +# elif defined(__COMPILER_KCC__) +# undef CENV_SYS_T20 /* Not quite right, but close enough */ +# define CENV_SYS_T20 1 +# endif +#endif + + +/* Derive composite switches - may not be entirely accurate, + but close enough. +*/ +#ifndef CENV_SYS_XBSD /* All modern BSD variants */ +# define CENV_SYS_XBSD (CENV_SYS_NETBSD|CENV_SYS_FREEBSD|CENV_SYS_OPENBSD) +#endif +#ifndef CENV_SYS_BSD /* For any BSD-generic stuff (TTY, time) */ +# define CENV_SYS_BSD (CENV_SYS_SUN|CENV_SYS_SOLARIS|CENV_SYS_BSDI \ + |CENV_SYS_XBSD|CENV_SYS_NEXT|CENV_SYS_DECOSF \ + |CENV_SYS_LINUX) +#endif +#define CENV_SYS_SVR4 0 /* XXX Later: (CENV_SYS_SOLARIS|CENV_SYS_DECOSF) ? */ +#define CENV_SYS_UNIX (CENV_SYS_V7|CENV_SYS_BSD|CENV_SYS_SVR4) /* Any Unix */ + +/* Specific OS Feature defs + This only has features of interest for KLH10 software. + */ +#ifndef CENV_SYSF_BSDTIMEVAL /* Has "timeval" struct & calls */ +# define CENV_SYSF_BSDTIMEVAL (CENV_SYS_UNIX && !CENV_SYS_V7) +#endif +#ifndef CENV_SYSF_TERMIOS /* Has termios(3) tty stuff */ +# define CENV_SYSF_TERMIOS (CENV_SYS_SOLARIS|CENV_SYS_XBSD|CENV_SYS_LINUX) +#endif +#ifndef CENV_SYSF_BSDTTY /* Has old BSD tty stuff */ +# define CENV_SYSF_BSDTTY (!CENV_SYSF_TERMIOS && CENV_SYS_BSD) +#endif +#ifndef CENV_SYSF_SIGSET /* Has sigsetops(3) and sigaction(2) */ +# define CENV_SYSF_SIGSET (CENV_SYS_DECOSF|CENV_SYS_SUN|CENV_SYS_SOLARIS \ + |CENV_SYS_XBSD|CENV_SYS_LINUX) +#endif +#ifndef CENV_SYSF_STRERROR /* Has strerror(3) */ +# define CENV_SYSF_STRERROR (CENV_SYS_DECOSF|CENV_SYS_SOLARIS|CENV_SYS_XBSD \ + |CENV_SYS_LINUX) +#endif +#ifndef CENV_SYSF_NANOSLEEP /* Has nanosleep(2) */ +# define CENV_SYSF_NANOSLEEP (CENV_SYS_DECOSF|CENV_SYS_SOLARIS|CENV_SYS_XBSD \ + |CENV_SYS_LINUX) +#endif + +#endif /* ifndef CENV_INCLUDED */ diff --git a/src/dpimp.c b/src/dpimp.c new file mode 100644 index 0000000..388be9f --- /dev/null +++ b/src/dpimp.c @@ -0,0 +1,2092 @@ +/* DPIMP.C - ARPANET IMP device emulator +*/ +/* $Id: dpimp.c,v 2.4 2001/11/19 10:31:57 klh Exp $ +*/ +/* Copyright © 1992-1999, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dpimp.c,v $ + * Revision 2.4 2001/11/19 10:31:57 klh + * Major revision of ARP code to do full ARP request/reply handling, + * needed for Linux; may come in handy for other ports. + * + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* + This is a program intended to be run as a child of the KLH10 +PDP-10 emulator, in order to provide an IP-only ethernet interface +that looks like a simplified IMP. It uses the host system's +packet-filtering mechanism to access the ethernet, and assumes that +only IP packets will be transferred in and out. This mechanism might +be one of: + NIT - SunOS Network Interface Tap. + DLPI - Solaris Data-Link-Provider Interface (the ugliest). + BPF - BSD packetfilter. + PFLT - OSF/1 packetfilter. + TUN - FreeBSD tunnel device (the simplest). + LNX - Linux "PF_PACKET" interface (the dumbest). + +In what follows, "NET" is understood to be one of the above. + + "Messages" to and from the IMP are sent over the standard DP +shared memory mechanism; these are composed of 8-bit bytes in exactly +the same order as if an IMP was sending or receiving them. Packets to +and from the NET are ethernet packets. + + There are actually two processes active, one which pumps data +from the NET to the IMP-output buffer, and another that pumps in the +opposite direction from an input buffer to the NET. Generally they +are completely independent. + + ----------------------------- + + The IP address used for the emulated host is whatever is +provided in the KLH10 configuration command for the LHDH device, which +interfaces to the IMP. This address must obviously be identical to +what the emulated host OS thinks it is! + + Ensuring that inbound IP traffic is directed to the right +place is relatively easy. First, the IP address for the emulated host +must conform to the architecture of the local ethernet that the native +host is on, so that routers/gateways will direct packets to the +correct LAN. At this point, ARP takes over. + + ARPs are handled by the native host OS. DPIMP arranges for +this by inserting an ARP entry into the kernel with the ATF_PUBL +(publish) flag set. This entry has the emulated host IP address paired with +the native host device ethernet address; the ATF_PUBL flag persuades +the native OS to act as a server in response to ARPs for that IP address. +As long as the emulated host IP address fits within the local network +structure, so that routers/gateways will direct packets to the right +LAN, this ARP entry ensures that the packets will arrive at the native +host's ethernet interface. Note: the code attempts to pick a reasonable +default for the local ethernet interface, unless a particular (and +possibly dedicated) interface is specified. + + The NET packet-filtering mechanism is used to filter out +IP-type ethernet packets where the IP datagram's destination matches +that of the emulated host. While these IP datagrams are also +processed further within the OS when using a shared interface, it +appears that unless forwarding is explicitly set up or the kernel +compiled as a gateway, these datagrams will just be dropped on the +floor, which is precisely what we want. + + Representing the source address in the IMP leader provided to +the emulated host (as a 24-bit host/imp field) is problematical, as is +the reverse mapping. Note that on receipt, only the 48-bit ethernet +address of the sender is known; the IP source address generally won't +be that of the last-hop sender. The ITS code doesn't really need to +pay attention to this field on input, but on output something has to be +picked. + + Outbound routing in general is more difficult. IP addresses +can be turned into local network addresses by using AF_INET type +addresses in the putmsg() call for NET output; this causes the output +interface to look the IP address up in the ARP tables, and do an actual +ARP if necessary. However, this only works for the local network, and +is useless for finding a router or gateway; in order to send packets +elsewhere, the correct IP address must be known for a gateway/router on +the local net. + (NIT WARNING! This actually DOES NOT WORK for NIT because the +nit_if stream output code checks for and only accepts messages with +AF_UNSPEC addresses, so any NIT output must be accompanied by an +ethernet destination address. There's a slim possibility that some +sort of "raw" socket output can be done using the NIT (via protosw +dispatch: pr_output entry in nitsw[] table for SOCK_RAW, pointed to by +nitdomain (AF_NIT). Invoked by raw_usrreq in net/raw_usrreq.c. Must +investigate... raw socket output uses sendto() to specify address +along with data.) + + One idea is to retain the use of ARPANET addresses for purposes +of talking between the emulated host and the DPIMP, such that only the +arpanet address of the host and a mythical prime gateway or two are known. +The main problem with this is that the ITS code would proceed to use its +ARPANET address in IP headers, which cannot be changed without revising +the IP checksum as well. + + The other method is to pretend that the local network is an IMP +class A network, and map the low 3 octets of IP addresses directly into +the 24-bit host/imp field. However, it will be necessary to assemble +an ITS that knows its true IP address on the local net, and change the +prime-gateway routing table (at IPGWTG in INET >) to know about the +local net router/gateway. Any IMP code that assumes it lives on the +ARPANET must likewise be changed, although this is not too hard. + A bigger pain is that this code also must be modified to know +about the actual subnet mask in use, so it doesn't attempt to directly +send stuff that actually should go to the gateway. Either ITS must know +the address architecture plus a default gateway, or DPIMP must. + If the local net is a well-behaved class A, B, or C net then +things may work well at the ITS level. If not (ie subnetting is in effect) +then perhaps DPIMP should deal with it. + + Compromise method: have ITS know its "real" IP address, so that +it will be inserted into all datagrams properly. However, disregard the +host/imp address in the IMP leader and always have DPIMP decide for itself +where to send the datagram based on the IP destination address. This +means DPIMP has to know the local subnetting arrangement and gateway routes +but this information can in principle be extracted from the kernel. +Under this scheme, it doesn't matter too much how the IMP leader +address is put together. However, just to have SOME consistent scheme, +we'll use: + IP octet 1 - Would go into NET field, but for now keep 0 + IP octet 2 - Host # + IP octet 3 - IMP # high byte + IP octet 4 - IMP # low byte + +AGH! Unfortunately it turns out that while the subnet mask can easily +be procured via an ioctl(), there is no way to get routing table +information other than by reading the kernel memory directly (which is +how netstat gets at the info). So it's very painful for DPIMP to do +the routing. Moreover, it will be inefficient for DPIMP to just use a +single default gateway if more than one is on the local network, +because redirects will not be recognized by DPIMP (unless even more +hair is added to intercept and understand them) and any attempt by ITS +to comply with the redirects it receives is useless as long as DPIMP +ignores the IMP leader addressing info. + + For now, let's build the IP address by slapping the 1st IP byte +on from the native host's IP address, and taking the rest from the host/imp +fields per scheme above. DPIMP then checks to see if it's a local net +address, and if so uses that IP address. If not (ITS thinks it's local, +but it isn't on right subnet) then DPIMP substitutes a single default +gateway address. This resolves some but not all of the problem. + +------------------------------------------- + +Another ARP screw: + On at some systems (OSF/1 for example) the native host's IP +address may not be listed in the kernel's ARP tables! This means that +when the emulated host attempts to send an IP packet to the native +host, its attempt to determine the ethernet destination address will +fail because the arp_look() call can't find it. + This can be fixed in a couple of ways: + +(1) Invoke the "arp" command as SU to insert the (perm, pub) entry + into the tables. + Pro: ensures set to right thing, DPIMP works without change. + Con: painful and subject to human error. + +(2) DPIMP to look up the e/n address for the native host's default IP + interface, and store that mapping in its own table. + Pro: convenient, works invisibly. + Con: if it picks wrong interface you're hosed invisibly. + +Algorithm to use: + - See if IMP ifc is same as default IP ifc. + - If yes - shared, not dedicated. + Get ether addr from PFFD as usual and copy into arptab cache. + - If no - dedicated, not shared. + See if native IP address can be found in OS ARP table. + If yes - save in cache, done. + If not - open another PF connection just to get EA for that + ifc. + +------------------------------------------- + +TUN to the rescue: + + That being said, the new BSD "tun" (IP tunnel) device solves +most of these problems by moving all of the IP routing mechanisms into +the native OS itself. Once initialized, constant ARP hacking is not +required, nor any ethernet header hacking -- in fact it should work +for any physical network! KLH10_NET_TUN should eventually become the +default for every OS that implements /dev/tun. + +*/ + +#include +#include +#include +#include +#include + +#include "klh10.h" /* Get config params */ + +/* This must precede any other OSD includes to ensure that DECOSF gets + the right flavor sockaddr (sigh) +*/ +#define OSN_USE_IPONLY 1 /* Only need IP stuff */ +#include "osdnet.h" /* OSD net defs, shared with DPNI20 */ + +#include /* For setpriority() */ +#include /* For mlockall() */ + +#include "dpimp.h" /* DPIMP specific defs, grabs DPSUP if needed */ + +#ifdef RCSID + RCSID(dpimp_c,"$Id: dpimp.c,v 2.4 2001/11/19 10:31:57 klh Exp $") +#endif + + +/* Structure of data shared between the two DPIMP forks; this is overlaid + on the "blob" within dpimp_s. + Currently only the ARP hackery uses this. + + For concurrency fans, the access mechanism implemented here is a variant + of Peterson's algorithm, which in turn is akin to Dekker's algorithm. + */ +struct dpimpsh_s { + /* Locking mechanism to control access. */ + int dpimpsh_lock[2]; /* Flag - trying to enter critical section */ + int dpimpsh_lockid; /* Locker's ID */ + + /* ARP cache, may or may not be needed */ + int dpimpsh_arpsiz; /* # of entries in ARP table */ + int dpimpsh_arprefs; /* crude timeout counter */ + struct arpent { + struct in_addr at_iaddr; + struct ether_addr at_eaddr; + int at_flags; /* ARPF_xxx flags, similar to ATF_xxx */ +#define ARPF_INUSE 0x1 +#define ARPF_COM 0x2 +#define ARPF_PERM 0x4 + int at_lastref; /* Value of arprefs at last ref */ + } dpimpsh_arptab[1]; /* Actually N entries! */ +}; + +#define DPIMPSH(dpimp) ((struct dpimpsh_s *)dpimp->dpimp_blob) + + +/* Globals */ + +struct dp_s dp; /* Device-Process struct for DP ops */ + +int cpupid; /* PID of superior CPU process */ +int chpid; /* PID of child (R proc) */ +int mylockid; /* Locker IDs: 1 for W, 0 for R */ +int othlockid; +int swstatus = TRUE; +int pffd; /* Packet-Filter FD (bidirectional) */ + +struct in_addr ehost_ip; /* Emulated host IP addr, net order */ +struct in_addr ihost_ip; /* IMP/Native host IP addr, net order */ +struct in_addr ihost_nm; /* IMP/Native host subnet netmask, net order */ +struct in_addr ihost_net; /* IMP/Native host net #, net order */ +struct in_addr gwdef_ip; /* IP addr of default prime gateway */ + +struct ether_addr ehost_ea; /* Emulated host ethernet addr */ +struct ether_addr ihost_ea; /* IMP/Native host ethernet addr */ +int eaflags = 0; +#define EAF_IHOST 01 /* ihost_ea is set - IMP/Native host EA */ +#define EAF_EHOST 02 /* ehost_ea is set - Emulated host EA */ + + +/* Debug flag reference. Use DBGFLG within functions that have "dpimp"; + * all others must use DP_DBGFLG. Both refer to the same location. + * Caching a local copy of the debug flag is a no-no because it lives + * in a shared memory location that may change at any time. + */ +#define DBGFLG (dpimp->dpimp_dpc.dpc_debug) +#define DP_DBGFLG (((struct dpimp_s *)dp.dp_adr)->dpimp_dpc.dpc_debug) + +/* Local predeclarations */ + +void imptohost(struct dpimp_s *); +void hosttoimp(struct dpimp_s *); + +void net_init(struct dpimp_s *); + +#if !KLH10_NET_TUN +void arp_init(struct dpimp_s *); +struct arpent *arptab_look(struct in_addr); +struct arpent *arp_look(struct in_addr, struct ether_addr *); +struct arpent *arp_tnew(struct in_addr addr, struct ether_addr *, int); +int arp_refreset(void); +void arp_set(struct arpent *, struct in_addr, struct ether_addr *, int); +void arp_req(struct in_addr *ipa); +void arp_gotrep(unsigned char *buf, int cnt); +void arp_reply(unsigned char *eap, unsigned char *iap); + +int hi_iproute(struct in_addr *ipa, unsigned char *lp, int cnt); +void ip_write(struct in_addr *, unsigned char *, int); +void ether_write(struct eth_header *, unsigned char *, int); +#endif /* !KLH10_NET_TUN */ + +void ihl_frag(int, unsigned char *); +void ihl_hhsend(struct dpimp_s *, int, unsigned char *); +void dumppkt(unsigned char *, int); + +/* Error and diagnostic output */ + +static const char progname_i[] = "dpimp"; +static const char progname_r[] = "dpimp-R"; +static const char progname_w[] = "dpimp-W"; +static const char *progname = progname_i; + +static void efatal(int num, char *fmt, ...) +{ + fprintf(stderr, "\n[%s: Fatal error: ", progname); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fputs("]\r\n", stderr); + + /* DP automatically kills any child as well. */ + dp_exit(&dp, num); +} + +static void esfatal(int num, char *fmt, ...) +{ + fprintf(stderr, "\r\n[%s: ", progname); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fprintf(stderr, " - %s]\r\n", dp_strerror(errno)); + + /* DP automatically kills any child as well. */ + dp_exit(&dp, num); +} + +static void dbprint(char *fmt, ...) +{ + fprintf(stderr, "[%s: ", progname); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fputs("]", stderr); +} + +static void dbprintln(char *fmt, ...) +{ + fprintf(stderr, "[%s: ", progname); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fputs("]\r\n", stderr); +} + +static void error(char *fmt, ...) +{ + fprintf(stderr, "\n[%s: ", progname); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fputs("]\r\n", stderr); +} + +static void syserr(int num, char *fmt, ...) +{ + fprintf(stderr, "\n[%s: ", progname); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fprintf(stderr, " - %s]\r\n", dp_strerror(num)); +} + +int initdebug = 0; + +int +main(int argc, char **argv) +{ + register struct dpimp_s *dpimp; /* Ptr to shared memory area */ + + /* Search for a "-debug" command-line argument so that we can start + debug output ASAP if necessary. + */ + if (argc > 1) { + int i; + for (i = 1; i < argc; ++i) { + if (strcmp(argv[i], "-debug") == 0) { + initdebug = TRUE; + break; + } + } + } + if (initdebug) + dbprint("Starting"); + + /* Right off the bat attempt to get the highest scheduling priority + ** we can, since a slow response will cause the 10 monitor to declare + ** the interface dead. + */ +#if CENV_SYS_SOLARIS || CENV_SYS_DECOSF || CENV_SYS_XBSD || CENV_SYS_LINUX + if (setpriority(PRIO_PROCESS, 0, -20) < 0) + syserr(errno, "Warning - cannot set high priority"); +#elif CENV_SYS_UNIX /* Try old generic Unix call */ + if (nice(-20) == -1) + syserr(errno, "Warning - cannot set high priority"); +#else + error("Warning - cannot set high priority"); +#endif + + /* Next priority is to quickly close the vulnerability window; + disable TTY cruft to ensure that any TTY hacking done by superior + process doesn't inadvertently kill us off. + */ + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + + if (initdebug) + dbprint("Started"); + + /* General initialization */ + if (geteuid() != 0) + efatal(1, "Must be superuser!"); + + if (!dp_main(&dp, argc, argv)) { + efatal(1, "DP init failed!"); + } + dpimp = (struct dpimp_s *)dp.dp_adr; /* Make for easier refs */ + + /* Verify that the structure version is compatible */ + if (dpimp->dpimp_ver != DPIMP_VERSION) { + efatal(1, "Wrong version of DPIMP: lhdh=%0lx dpimp=%0lx", + (long)dpimp->dpimp_ver, (long)DPIMP_VERSION); + } + + /* Now can access DP args! + From here on we can use DBGFLG, which is actually a shared + memory reference that dpimp points to. Check here to accomodate the + case where it's not already set but "-debug" was given as a command + arg; leave it alone if already set since the exact bits have + significance. + */ + if (initdebug && !DBGFLG) + DBGFLG = 1; + if (DBGFLG) + dbprint("DP inited"); + + /* Always attempt to lock memory since the DP processes are fairly + ** small, must respond quickly, and SU mode is more or less guaranteed. + ** Skip it only if dp_main() already did it for us. + */ +#if CENV_SYS_DECOSF || CENV_SYS_SOLARIS || CENV_SYS_LINUX + if (!(dpimp->dpimp_dpc.dpc_flags & DPCF_MEMLOCK)) { + if (mlockall(MCL_CURRENT|MCL_FUTURE) != 0) { + dbprintln("Warning - cannot lock memory"); + } + } +#endif + + /* Now set up legacy variables based on parameters passed through + shared DP area. + */ + memcpy((void *)&ehost_ip, dpimp->dpimp_ip, 4); /* Host IP addr */ + memcpy((void *)&gwdef_ip, dpimp->dpimp_gw, 4); /* Default GW addr */ + memcpy((void *)&ehost_ea, dpimp->dpimp_eth, 6); /* Host Ether addr */ + + /* IMP must always have IP address specified! */ + if (memcmp(dpimp->dpimp_ip, "\0\0\0\0", 4) == 0) + efatal(1, "no IP address specified"); + + /* Canonicalize ARP hackery flags */ + if (dpimp->dpimp_doarp == TRUE) /* If simply set to "true", */ + dpimp->dpimp_doarp = DPIMP_ARPF_PUBL | /* then do all */ + DPIMP_ARPF_PERM | DPIMP_ARPF_OCHK; + + /* Set up shared area for the DPIMP forks */ + DPIMPSH(dpimp)->dpimpsh_lock[0] = 0; + DPIMPSH(dpimp)->dpimpsh_lock[1] = 0; + + /* See if EA provided */ + if (memcmp((void *)&ehost_ea, "\0\0\0\0\0\0", 6) != 0) { + eaflags |= EAF_EHOST; + } + + /* Initialize various network info */ + net_init(dpimp); + + +#if !KLH10_NET_TUN + /* TUN may not have an ethernet address associated with it; + not sure what to do if DPIMP turns out to need one. + */ + + /* See if ether address needs to be set */ + if (memcmp((void *)&ihost_ea, "\0\0\0\0\0\0", 6) != 0) + eaflags |= EAF_IHOST; + switch (eaflags & (EAF_IHOST|EAF_EHOST)) { + case 0: + efatal(1, "no ethernet address"); + case EAF_IHOST: + break; /* OK, don't need anything special */ + case EAF_EHOST: + error("couldn't get native ether addr, using specified"); + ea_set(&ihost_ea, &ehost_ea); + break; + case EAF_IHOST|EAF_EHOST: + if (memcmp((void *)&ihost_ea, (void *)&ehost_ea, 6) == 0) + break; /* OK, addresses are same */ + /* Ugh, specified an EA address different from one actually + ** in use by interface! For now, don't allow clobberage. + */ + efatal(1, "changing ethernet addr is disallowed"); + break; + } +#endif /* !KLH10_NET_TUN */ + + /* Make this a status (rather than debug) printout? */ + if (swstatus) { + char ipbuf[OSN_IPSTRSIZ]; + char eabuf[OSN_EASTRSIZ]; + + dbprintln("ifc \"%s\" => ether %s", + dpimp->dpimp_ifnam, + eth_adrsprint(eabuf, (unsigned char *)&ihost_ea)); + dbprintln(" inet %s", + ip_adrsprint(ipbuf, (unsigned char *)&ihost_ip)); + dbprintln(" netmask %s", + ip_adrsprint(ipbuf, (unsigned char *)&ihost_nm)); + dbprintln(" net %s", + ip_adrsprint(ipbuf, (unsigned char *)&ihost_net)); + dbprintln(" HOST: %s", + ip_adrsprint(ipbuf, (unsigned char *)&ehost_ip)); + dbprintln(" gwdef %s", + ip_adrsprint(ipbuf, (unsigned char *)&gwdef_ip)); + } + + /* Init ARP stuff - ensure can talk to native host. + ** Set up ARP entry so hardware host knows about our IP address and + ** can respond to ARP requests for it. + */ +#if !KLH10_NET_TUN /* If TUN, already done by osn_pfinit */ + arp_init(dpimp); + if (!osn_arp_stuff((unsigned char *)&ehost_ip, + (unsigned char *)&ihost_ea, TRUE)) /* Set us up */ + esfatal(1, "OSN_ARP_STUFF failed"); +#endif + + /* Now start up a child process to handle input */ + if (DBGFLG) + dbprint("Forking R process"); + if ((chpid = fork()) < 0) + esfatal(1, "fork failed"); + if (chpid == 0) { + /* Child process. + ** Child inherits signal handlers, which is what we want here. + */ + mylockid = 0; + othlockid = 1; + + /* Fix up xfer mechanism so ACK of DP input goes to correct proc */ + dp.dp_adr->dpc_frdp.dpx_donpid = getpid(); + + /* And ensure its memory is locked too, since the lockage isn't + ** inherited over a fork(). Don't bother warning if it fails. + */ +#if CENV_SYS_DECOSF || CENV_SYS_SOLARIS || CENV_SYS_LINUX + (void) mlockall(MCL_CURRENT|MCL_FUTURE); +#endif + progname = progname_r; /* Reset progname to indicate identity */ + imptohost(dpimp); /* Child process handles input */ + } + mylockid = 1; + othlockid = 0; + progname = progname_w; /* Reset progname to indicate identity */ + + hosttoimp(dpimp); /* Parent process handles output */ + + return 1; /* Never returns, but placate compiler */ +} + +/* NET_INIT - Initialize net-related variables, +** given network interface we'll use. +*/ +void +net_init(register struct dpimp_s *dpimp) +{ + struct ifreq ifr; + +#if 1 /* This code is identical to dpni20 - merge in osdnet? */ + + /* Ensure network device name, if specified, isn't too long */ + if (dpimp->dpimp_ifnam[0] && (strlen(dpimp->dpimp_ifnam) + >= sizeof(ifr.ifr_name))) { + esfatal(0, "interface name \"%s\" too long - max %d", + dpimp->dpimp_ifnam, (int)sizeof(ifr.ifr_name)); + } + + /* Determine network device to use, if none was specified (this only + ** works for shared devices, as dedicated ones will be "down" and + ** cannot be found by iftab_init). + ** Also grab native IP and ethernet addresses, if ARP might need them. + */ + if ((!dpimp->dpimp_ifnam[0] && !dpimp->dpimp_dedic) + || (dpimp->dpimp_doarp & DPIMP_ARPF_OCHK)) { + if (osn_iftab_init(IFTAB_IPS) <= 0) + esfatal(0, "Couldn't find interface information"); + + /* Found at least one! Pick first one, if a default is needed. */ + if (!dpimp->dpimp_ifnam[0]) { + struct ifent *ife = osn_ipdefault(); + if (!ife) + esfatal(0, "Couldn't find default interface"); + if (strlen(ife->ife_name) >= sizeof(dpimp->dpimp_ifnam)) + esfatal(0, "Default interface name \"%s\" too long, max %d", + ife->ife_name, (int)sizeof(dpimp->dpimp_ifnam)); + + strcpy(dpimp->dpimp_ifnam, ife->ife_name); + if (swstatus) + dbprintln("Using default interface \"%s\"", dpimp->dpimp_ifnam); + } + } +#endif + + /* Now set remaining stuff */ + + /* Find IMP host's IP address for this interface */ + if (!osn_ifipget(-1, dpimp->dpimp_ifnam, (unsigned char *)&ihost_ip)) { + efatal(1,"osn_ifipget failed for \"%s\"", dpimp->dpimp_ifnam); + } + + /* Ditto for its network mask */ + if (!osn_ifnmget(-1, dpimp->dpimp_ifnam, (unsigned char *)&ihost_nm)) { + efatal(1,"osn_ifnmget failed for \"%s\"", dpimp->dpimp_ifnam); + } + + /* Now set remaining stuff */ + ihost_net.s_addr = ihost_nm.s_addr & ihost_ip.s_addr; /* Local net */ + + /* Either move this check up much earlier, or find a way to + ** query OS for a default gateway. + */ + if (gwdef_ip.s_addr == -1 || gwdef_ip.s_addr == 0) + efatal(1, "No default prime gateway specified"); + + /* Set up appropriate net fd and packet filter. + ** Should also determine interface's ethernet addr, if possible, + ** and set ihost_ea. + */ + { + struct osnpf npf; + + npf.osnpf_ifnam = dpimp->dpimp_ifnam; + npf.osnpf_dedic = FALSE; /* Force filtering always! */ + npf.osnpf_rdtmo = dpimp->dpimp_rdtmo; + npf.osnpf_backlog = dpimp->dpimp_backlog; + npf.osnpf_ip.ia_addr = ehost_ip; + /* Ether addr is both a potential arg and a returned value; + the packetfilter open may use and/or change it. + */ + ea_set(&npf.osnpf_ea, dpimp->dpimp_eth); /* Set requested ea if any */ + pffd = osn_pfinit(&npf, (void *)dpimp); /* Will abort if fails */ + ea_set(&ihost_ea, &npf.osnpf_ea); /* Copy actual ea if one */ + } +} + +/* The DPIMP packet filter must implement the following test: +** +** if ((dest_IP_addr == ITS_IP_addr) && (ethertype == IP)) { success; } +** +** For efficiency, the strictest test is done first -- the low shortword +** of the IP address, which is the most likely to differ from that of the +** native host. We do this even before checking whether the packet contains +** IP data; if it's too short, it's rejected anyway. +*/ + +/* Common packetfilter definitions - for all but BPF */ + +#if KLH10_NET_PFLT || KLH10_NET_NIT || KLH10_NET_DLPI + +#if KLH10_NET_PFLT +# define OSN_PFSTRUCT enfilter +# define PF_PRIO enf_Priority +# define PF_FLEN enf_FilterLen +# define PF_FILT enf_Filter +#elif (KLH10_NET_DLPI || KLH10_NET_NIT) +# define OSN_PFSTRUCT packetfilt +# define PF_PRIO Pf_Priority +# define PF_FLEN Pf_FilterLen +# define PF_FILT Pf_Filter +#endif + +struct OSN_PFSTRUCT pfilter; + +static void pfshow(struct OSN_PFSTRUCT *); + +/* Build packet filter to pass on only IP packets for given IP addr */ + +struct OSN_PFSTRUCT * +pfbuild(void *arg, struct in_addr *ipa) +{ + register struct dpimp_s *dpimp = (struct dpimp_s *)arg; + register unsigned short *p; + register union ipaddr *uipa = (union ipaddr *)ipa; + register struct OSN_PFSTRUCT *pfp = &pfilter; + + p = pfp->PF_FILT; /* Get addr of filter (length ENMAXFILTERS) */ + + *p++ = ENF_PUSHWORD + PKSWOFF_IPDEST+1; + *p++ = ENF_PUSHLIT | ENF_CAND; /* Compare low wds of IP addrs */ + *p++ = htons((uipa->ia_octet[2]<<8) | (uipa->ia_octet[3])); + + *p++ = ENF_PUSHWORD + PKSWOFF_IPDEST; + *p++ = ENF_PUSHLIT | ENF_CAND; /* Compare high wds of IP addrs */ + *p++ = htons((uipa->ia_octet[0]<<8) | (uipa->ia_octet[1])); + + *p++ = ENF_PUSHWORD + PKSWOFF_ETYPE; /* Verify IP packet */ + *p++ = ENF_PUSHLIT | ENF_EQ; + *p++ = htons(ETHERTYPE_IP); + + pfp->PF_FLEN = p - pfp->PF_FILT; /* Set # of items on list */ + pfp->PF_PRIO = 128; /* Pick middle of 0-255 range */ + /* "Ignored", but RARPD recommends > 2 */ + + if (DBGFLG) /* If debugging, print out resulting filter */ + pfshow(pfp); + + return pfp; +} + +/* Debug auxiliary to print out packetfilter we composed. +*/ +static void +pfshow(struct OSN_PFSTRUCT *pf) +{ + int i; + + fprintf(stderr,"[%s: kernel packetfilter pri %d, len %d:", progname, + pf->PF_PRIO, pf->PF_FLEN); + for (i = 0; i < pf->PF_FLEN; ++i) + fprintf(stderr, " %04X", pf->PF_FILT[i]); + fprintf(stderr, "]\r\n"); +} + +#endif /* KLH10_NET_PFLT || KLH10_NET_NIT || KLH10_NET_DLPI */ + +#if KLH10_NET_BPF + +/* +** BPF filter program stuff. +** Note that you can also obtain a specific filter program for a given +** expression by using tcpdump(1) with the -d option, for example: +** tcpdump -d -s 1514 ip dst host 1.2.3.4 +** produces: +*/ +#if 0 + { 0x20, 0, 0, 0x0000001e }, /* (000) ld [30] */ + { 0x15, 0, 3, 0x01020304 }, /* (001) jeq #0x1020304 jt 2 jf 5 */ + { 0x28, 0, 0, 0x0000000c }, /* (002) ldh [12] */ + { 0x15, 0, 1, 0x00000800 }, /* (003) jeq #0x800 jt 4 jf 5 */ + { 0x06, 0, 0, 0x000005ea }, /* (004) ret #1514 */ + { 0x06, 0, 0, 0x00000000 }, /* (005) ret #0 */ +#endif + +#define OSN_PFSTRUCT bpf_program +#define PF_FLEN bf_len +#define PF_FILT bf_insns + +#define BPF_PFMAX 50 /* Max instructions in BPF filter */ +struct bpf_insn bpf_pftab[BPF_PFMAX]; +struct bpf_program bpf_pfilter = { 0, + bpf_pftab }; + +struct bpf_insn bpf_stmt(unsigned short code, bpf_u_int32 k) +{ + struct bpf_insn ret; + ret.code = code; + ret.jt = 0; + ret.jf = 0; + ret.k = k; + return ret; +} + +struct bpf_insn bpf_jump(unsigned short code, bpf_u_int32 k, + unsigned char jt, unsigned char jf) +{ + struct bpf_insn ret; + ret.code = code; + ret.jt = jt; + ret.jf = jf; + ret.k = k; + return ret; +} + +/* BPF simple Loads */ +#define BPFI_LD(a) bpf_stmt(BPF_LD+BPF_W+BPF_ABS,(a)) /* Load word P[a:4] */ +#define BPFI_LDH(a) bpf_stmt(BPF_LD+BPF_H+BPF_ABS,(a)) /* Load short P[a:2] */ +#define BPFI_LDB(a) bpf_stmt(BPF_LD+BPF_B+BPF_ABS,(a)) /* Load byte P[a:1] */ + +/* BPF Jumps and skips */ +#define BPFI_J(op,k,t,f) bpf_jump(BPF_JMP+(op)+BPF_K,(k),(t),(f)) +#define BPFI_JEQ(k,n) BPFI_J(BPF_JEQ,(k),(n),0) /* Jump if A == K */ +#define BPFI_JNE(k,n) BPFI_J(BPF_JEQ,(k),0,(n)) /* Jump if A != K */ +#define BPFI_JGT(k,n) BPFI_J(BPF_JGT,(k),(n),0) /* Jump if A > K */ +#define BPFI_JLE(k,n) BPFI_J(BPF_JGT,(k),0,(n)) /* Jump if A <= K */ +#define BPFI_JGE(k,n) BPFI_J(BPF_JGE,(k),(n),0) /* Jump if A >= K */ +#define BPFI_JLT(k,n) BPFI_J(BPF_JGE,(k),0,(n)) /* Jump if A < K */ +#define BPFI_JDO(k,n) BPFI_J(BPF_JSET,(k),(n),0) /* Jump if A & K */ +#define BPFI_JDZ(k,n) BPFI_J(BPF_JSET,(k),0,(n)) /* Jump if !(A & K) */ + +#define BPFI_CAME(k) BPFI_JEQ((k),1) /* Skip if A == K */ +#define BPFI_CAMN(k) BPFI_JNE((k),1) /* Skip if A != K */ +#define BPFI_CAMG(k) BPFI_JGT((k),1) /* Skip if A > K */ +#define BPFI_CAMLE(k) BPFI_JLE((k),1) /* Skip if A <= K */ +#define BPFI_CAMGE(k) BPFI_JGE((k),1) /* Skip if A >= K */ +#define BPFI_CAML(k) BPFI_JLT((k),1) /* Skip if A < K */ +#define BPFI_TDNN(k) BPFI_JDO((k),1) /* Skip if A & K */ +#define BPFI_TDNE(k) BPFI_JDZ((k),1) /* Skip if !(A & K) */ + +/* BPF Returns */ +#define BPFI_RET(n) bpf_stmt(BPF_RET+BPF_K, (n)) /* Return N bytes */ +#define BPFI_RETFAIL() BPFI_RET(0) /* Failure return */ +#define BPFI_RETWIN() BPFI_RET((u_int)-1) /* Success return */ + + +static void pfshow(struct OSN_PFSTRUCT *); + +struct OSN_PFSTRUCT * +pfbuild(void *arg, struct in_addr *ipa) +{ + register struct dpimp_s *dpimp = (struct dpimp_s *)arg; + register unsigned char *ucp = (unsigned char *)ipa; + register struct OSN_PFSTRUCT *pfp = &bpf_pfilter; + register struct bpf_insn *p; + + p = pfp->PF_FILT; /* Point to 1st instruction in BPF program */ + + /* We're interested in IP (and thus ARP as well) packets. + This is assumed to be the LAST part of the filter, thus + it must either leave the correct result on the stack, or + ensure it is empty (if accepting the packet). + */ + if (ipa->s_addr != 0) { + + /* Want to pass ARP replies as well, so we can see responses to any + ** ARPs we send out? + ** NOTE!!! ARP *requests* are not passed! The assumption is that + ** osn_arp_stuff() will have ensured that the host platform + ** proxy-answers requests for our IP address. + */ + *p++ = BPFI_LDH(PKBOFF_ETYPE); /* Load ethernet type field */ + *p++ = BPFI_CAMN(ETHERTYPE_ARP); /* Skip unless ARP packet */ + *p++ = BPFI_RETWIN(); /* If ARP, win now! */ + + /* If didn't pass, check for our IP address */ + *p++ = BPFI_LD(PKBOFF_IPDEST); /* Get IP dest address */ + *p++ = BPFI_CAME(ntohl(ehost_ip.s_addr)); /* Skip if matches */ + *p++ = BPFI_RETFAIL(); /* Nope, fail */ + + /* Passed IP check, one last thing... */ + *p++ = BPFI_LDH(PKBOFF_ETYPE); /* Load ethernet type field */ + *p++ = BPFI_CAMN(ETHERTYPE_IP); /* Skip unless IP packet */ + *p++ = BPFI_RETWIN(); /* If IP, win now! */ + *p++ = BPFI_RETFAIL(); /* Nope, fail */ + + } else { + /* If not doing IP, fail at this point because the packet + doesn't match any of the desired types. + */ + *p++ = BPFI_RETFAIL(); /* Fail */ + } + + pfp->PF_FLEN = p - pfp->PF_FILT; /* Set # of items on list */ + + if (DBGFLG) /* If debugging, print out resulting filter */ + pfshow(pfp); + return pfp; +} + + +/* Debug auxiliary to print out packetfilter we composed. +*/ +static void +pfshow(struct OSN_PFSTRUCT *pf) +{ + int i; + + fprintf(stderr, "[dpimp: kernel packetfilter pri <>, len %d:\r\n", + /* pf->PF_PRIO, */ pf->PF_FLEN); + for (i = 0; i < pf->PF_FLEN; ++i) + fprintf(stderr, "%04X %2d %2d %0X\r\n", + pf->PF_FILT[i].code, + pf->PF_FILT[i].jt, + pf->PF_FILT[i].jf, + pf->PF_FILT[i].k); + fprintf(stderr, "]\r\n"); +} +#endif /* KLH10_NET_BPF */ + +#if KLH10_NET_LNX + +/* Because until very recently LNX had no kernel packet filtering, must do it + manually. Ugh! + + Call this even when using a dedicated interface, since only IP stuff + should be passed through the IMP. + + Returns TRUE if packet OK, FALSE if it should be dropped. + Note that the code parallels that for pfbuild(). +*/ +int lnx_filter(register struct dpimp_s *dpimp, + unsigned char *bp, + int cnt) +{ + /* Code assumes buffer is at least shortword-aligned. */ + register unsigned short *sp = (unsigned short *)bp; + register unsigned short etyp; + + /* Get ethernet protocol type. + Could also test packet length, but for now assume higher level + will take care of those checks. + */ + etyp = ntohs(sp[PKSWOFF_ETYPE]); + switch (etyp) { + +#if 1 /* Must pass on ARP processing (Linux doesn't proxy ARP for us!!) */ + case ETHERTYPE_ARP: + return TRUE; +#endif + case ETHERTYPE_IP: + /* For IP packet, return TRUE if IP destination matches ours */ + return (memcmp(dpimp->dpimp_ip, bp + PKBOFF_IPDEST, 4) == 0); + +#if 0 /* No other types allowed through IMP */ + /* Check for DECNET protocol types if requested. + ** The following are the known types: + ** 6001 DNA/MOP + ** 6002 RmtCon + ** 6003 DECnet + ** 6004 LAT + ** 6016 ANF-10 (T10 only; not DECNET) + ** 9000 Loopback (?) + */ + case 0x6001: /* DNA/MOP */ + case 0x6002: /* RmtCon */ + case 0x6003: /* DECnet */ + case 0x6004: /* LAT */ + case 0x6016: /* ANF-10 (T10 only; not DECNET) */ + case 0x9000: /* Loopback (?) */ + return (dpni->dpni_decnet); /* TRUE if wanted Decnet stuff */ + + default: + /* Test for an IEEE 802.3 packet with a specific dst/src LSAP. + Packet is 802.3 if type field is actually packet length -- in which + case it will be 1 <= len <= 1500 (note 1514 is max, of which header + uses 6+6+2=14). + + Dst/src LSAPs are in the 1st shortwd after packet length. + */ + if (etyp <= 1500 + && (dpni->dpni_attrs & DPNI20F_LSAP) + && (dpni->dpni_lsap == sp[PKSWOFF_SAPS])) + return TRUE; + break; +#endif /* 0 */ + + } + return FALSE; +} + +#endif /* KLH10_NET_LNX */ + + +#if !KLH10_NET_TUN + +/* ARP hacking code. Originally modelled after old BSD ARP stuff. */ + +/* Structure of DPIMP's ARP cache: + + The ARP cache is a simple table that is entered by means of a hash +function, but which has no separate bucket chains -- everything is on +the same "chain". If a desired entry is not found at the first hash, +all succeeding entries are checked until either an empty entry is hit +or the entire table is checked. This works because entries are never +flushed once entered. + + The rationale for this is that doing an external ARP lookup is +much more expensive than the time to do a full table scan, so we never +want to re-use an entry unless the table is full, and we want to use +all possible entries. + + If an ITS is ever brought up that becomes consistently busy enough +to thrash on the ARP table then a more sophisticated algorithm can be +used. + + */ + +#if 1 /* New stuff */ + +/* Set up by init for easier reference */ +static struct dpimpsh_s *arpp; +static struct arpent *arptab_lim; + +#define ARPTAB_HASH(max,a) \ + ((unsigned long)(a) % (max)) + +#else /* Old stuff - temporarily saved */ + +#define ARPTAB_BSIZ 6 /* bucket size */ +#define ARPTAB_NB 31 /* number of buckets (prime) */ +#define ARPTAB_SIZE (ARPTAB_BSIZ * ARPTAB_NB) +struct arpent arptab[ARPTAB_SIZE]; + +#define ARPTAB_HASH(a) \ + ((unsigned long)(a) % ARPTAB_NB) + +#define ARPTAB_LOOK(at,addr) { \ + register int n; \ + at = &arptab[ARPTAB_HASH(addr.s_addr) * ARPTAB_BSIZ]; \ + for (n = 0 ; n < ARPTAB_BSIZ ; n++,at++) \ + if (at->at_iaddr.s_addr == addr.s_addr) \ + break; \ + if (n >= ARPTAB_BSIZ) \ + at = 0; \ +} +#endif /* 0 */ + + +/* ARP_INIT +** Set up our own ARP cache, and ensure that native host is in +** it. Must be called after packetfilter already opened. +*/ +void +arp_init(struct dpimp_s *dpimp) +{ + register struct dpimpsh_s *dsh = DPIMPSH(dpimp); + struct ether_addr ea; + struct arpent *at; + + /* Init statics. Find # entries available in shared-area ARP table, + which is assumed to have been already cleared. + Note we add 1 because 1st entry is already in dpimpsh_s. + */ + arpp = dsh; + dsh->dpimpsh_arpsiz = 1 + ( + (dpimp->dpimp_blobsiz <= sizeof(struct dpimpsh_s)) + ? 0 + : ((dpimp->dpimp_blobsiz - sizeof(struct dpimpsh_s)) + / sizeof(struct arpent))); + arptab_lim = &dsh->dpimpsh_arptab[dsh->dpimpsh_arpsiz]; + + if (at = arp_look(ihost_ip, &ea)) { + /* It's now there, ensure it stays there */ + at->at_flags |= ARPF_PERM; + return; + } + /* Not found in ARP cache or OS table */ + + /* For now, assume shared and stuff an entry in our cache! */ + if (swstatus) + dbprintln("no native ARP entry, assuming shared ifc"); + + /* Store entry, say complete & permanent */ + (void) arp_tnew(ihost_ip, &ihost_ea, ARPF_PERM|ARPF_COM); +} + +/* ARP_REFRESET - reset all reference stamps to compensate for + * overflow of the main ref counter. + * This will be a rare event, so rather than trying to do anything + * clever, just clear everything so all entries start from the + * same place. This also obviates any need to lock since it doesn't + * matter if it's done twice. + */ +int +arp_refreset(void) +{ + register struct dpimpsh_s *dsh = arpp; + register struct arpent *at = &dsh->dpimpsh_arptab[0]; + register int max = dsh->dpimpsh_arpsiz; + register int i; + + for (i = 0; i < max; i++, at++) { + at->at_lastref = 0; + } + return dsh->dpimpsh_arprefs = 1; +} + + +struct arpent * +arptab_look(struct in_addr addr) +{ + register struct dpimpsh_s *dsh = arpp; + register int i; + register int max = dsh->dpimpsh_arpsiz; + register struct arpent *at = + &dsh->dpimpsh_arptab[ARPTAB_HASH(max, addr.s_addr)]; + + for (i = 0; i < max; i++, at++) { + if (at >= arptab_lim) + at = &dsh->dpimpsh_arptab[0]; + if (at->at_flags == 0) + break; + if (at->at_iaddr.s_addr == addr.s_addr) + return at; + } + return NULL; /* Table full or hit empty entry */ +} + + +/* + * Enter a new address in arptab, pushing out the oldest entry + * from the bucket if there is no room. + * This always succeeds since no bucket can be completely filled + * with permanent entries. + * If new entry matches an existing one, always replaces it; addr may + * have changed! + */ +struct arpent * +arp_tnew(struct in_addr addr, + struct ether_addr *eap, + int flags) +{ + register struct dpimpsh_s *dsh = arpp; + register int i; + register int max = dsh->dpimpsh_arpsiz; + register struct arpent *at = + &dsh->dpimpsh_arptab[ARPTAB_HASH(max, addr.s_addr)]; + + int oldest = dsh->dpimpsh_arprefs; + register struct arpent *ato = NULL; + + for (i = 0; i < max; i++, at++) { + if (at >= arptab_lim) + at = &dsh->dpimpsh_arptab[0]; + if (at->at_flags == 0) + break; /* Found an empty entry */ + if (at->at_iaddr.s_addr == addr.s_addr) + break; /* Matches existing */ + if (at->at_flags & ARPF_PERM) + continue; /* Never replace this */ + if (at->at_lastref < oldest) { + oldest = at->at_lastref; + ato = at; + } + } + if (i >= max) { /* No empty entry found? */ + if (ato == NULL) { + efatal(1, "ARP table choked?!"); + } + at = ato; /* Re-use oldest entry */ + } + + arp_set(at, addr, eap, + (at->at_flags & ARPF_PERM) /* Preserve ATF_PERM if old entry */ + | ARPF_INUSE | flags); + return at; +} + +/* ARP_SET - Set an ARP cache entry. Done in one place to centralize + * the update access control, even though it's quite simple. + */ +void +arp_set(register struct arpent *at, + struct in_addr addr, + struct ether_addr *eap, + int flags) +{ + register struct dpimpsh_s *dsh = arpp; + + /* Get write lock */ + dsh->dpimpsh_lock[mylockid] = TRUE; + dsh->dpimpsh_lockid = mylockid; + while (dsh->dpimpsh_lock[othlockid] /* Spin wait if other has it */ + && (dsh->dpimpsh_lockid != mylockid)); + + /* Start critical section */ + at->at_flags = flags; + if (at->at_flags & ARPF_COM) /* Use EA only if now complete */ + at->at_eaddr = *eap; + else + ea_clr(&at->at_eaddr); + at->at_iaddr = addr; + at->at_lastref = dsh->dpimpsh_arprefs; + /* End critical section */ + + dsh->dpimpsh_lock[mylockid] = FALSE; +} + + + + +/* ARP_LOOK - Look up Ethernet address given IP address. +** If not in our own cache, checks system. +** If not in system, fails. +*/ +struct arpent * +arp_look(struct in_addr ip, + struct ether_addr *eap) +{ + register struct arpent *at; + + at = arptab_look(ip); /* Look up IP addr */ + if (at && (at->at_flags & ARPF_COM)) { + register int i; + + /* Exists and complete */ + ea_set(eap, &(at->at_eaddr)); /* Return ether addr */ + + /* Note at_lastref is modified here without locking; this is OK */ + if ((i = ++(arpp->dpimpsh_arprefs)) < 0) + i = arp_refreset(); + at->at_lastref = i; + return at; + } + + /* Not found in our cache or not yet resolved, try OS query. */ + if (osn_iftab_arpget(ip, (unsigned char *)eap) /* Try table lookup */ + || osn_arp_look(&ip, (unsigned char *)eap)) { /* Attempt OS lookup */ + at = arp_tnew(ip, eap, ARPF_COM); /* Won! Store in our cache */ + if (swstatus) { + char ipbuf[OSN_IPSTRSIZ]; + char eabuf[OSN_EASTRSIZ]; + dbprintln("ARP cached %s = %s", + ip_adrsprint(ipbuf, (unsigned char *)&ip), + eth_adrsprint(eabuf, (unsigned char *)eap)); + } + return at; + } + return NULL; +} + + +/* ARP_REQ - Generates and sends ARP request. + Must remember the fact in our cache, so can process reply ourself + if any is received. +*/ +void +arp_req(struct in_addr *ipa) +{ + static int ethbuild = 0, arpbuild = 0; + static struct eth_header eh; + static struct ether_arp arp; + register struct arpent *at; + struct ether_addr ea; + + /* Store request in cache */ + memset((char *)&ea, 0, sizeof(ea)); + at = arp_tnew(*ipa, &ea, 0); /* Say incomplete with 0 flag */ + + /* Build ethernet header if haven't already */ + if (!ethbuild) { + memset(eh_dptr(&eh), 0xff, /* Set dest broadcast addr */ + ETHER_ADRSIZ); + eh_sset(&eh, &ihost_ea); /* Set ether source addr */ + eh_tset(&eh, ETHERTYPE_ARP); + ethbuild = TRUE; + } + + /* Now put together the ARP packet */ + if (!arpbuild) { + arp.arp_hrd = htons(ARPHRD_ETHER); /* Set hdw addr format */ + arp.arp_pro = htons(ETHERTYPE_IP); /* Set ptcl addr fmt */ + arp.arp_hln = sizeof(arp.arp_sha); /* Hdw address len */ + arp.arp_pln = sizeof(arp.arp_spa); /* Ptcl address len */ + arp.arp_op = htons(ARPOP_REQUEST); /* Type REQUEST */ + ea_set(arp.arp_sha, &ihost_ea); /* Sender hdw addr */ + memcpy((char *)arp.arp_spa, /* Sender IP addr */ + (char *)&ihost_ip, sizeof(arp.arp_sha)); + arpbuild = TRUE; + } + + /* Now do only thing that varies -- set IP addr we're looking up. */ + memcpy((char *)arp.arp_tpa, /* Target IP addr */ + (char *)ipa, sizeof(arp.arp_tpa)); + + /* Now send it! */ + if (swstatus) { + char ipbuf[OSN_IPSTRSIZ]; + dbprintln("ARP req %s", ip_adrsprint(ipbuf, (unsigned char *)ipa)); + } + + ether_write(&eh, (unsigned char *)&arp, sizeof(arp)); +} + + +/* ARP_GOTREP - Process an ARP Reply + If it matches a request we already sent out, remember its + information. Next time we try sending a packet to that IP address + we'll find the entry. + Should we respond to ARP requests (proxy ARP)??? +*/ + +#define ARP_PKTSIZ (sizeof(struct ether_header) + sizeof(struct ether_arp)) + +void +arp_gotrep(unsigned char *buf, int cnt) +{ + register struct ether_arp *aa; + register struct arpent *at; + struct arpent ent; + + if (DP_DBGFLG) { + char eabuf[OSN_EASTRSIZ]; + dbprintln("Got ARP from %s", eth_adrsprint(eabuf, eh_sptr(buf))); + } + + /* Verify packet is an ether ARP reply */ + if (cnt < ARP_PKTSIZ) { + if (DP_DBGFLG) + dbprintln("Dropped ARP, size %d < %d", cnt, (int)ARP_PKTSIZ); + return; + } + aa = (struct ether_arp *)(buf + ETHER_HDRSIZ); + if (aa->arp_hrd != htons(ARPHRD_ETHER)) { /* Check hdw addr format */ + if (DP_DBGFLG) + dbprintln("Dropped ARP, hrd %0x != %0x", + aa->arp_hrd, htons(ARPHRD_ETHER)); + return; + } + if (aa->arp_pro != htons(ETHERTYPE_IP)) { /* Check ptcl addr fmt */ + if (DP_DBGFLG) + dbprintln("Dropped ARP, pro %0x != %0x", + aa->arp_pro, htons(ETHERTYPE_IP)); + return; + } + if (aa->arp_hln != sizeof(aa->arp_sha)) { /* Check Hdw address len */ + if (DP_DBGFLG) + dbprintln("Dropped ARP, hln %d != %d", + aa->arp_hln, sizeof(aa->arp_sha)); + return; + } + if (aa->arp_pln != sizeof(aa->arp_spa)) { /* Check Ptcl address len */ + if (DP_DBGFLG) + dbprintln("Dropped ARP, pln %d != %d", + aa->arp_pln, sizeof(aa->arp_spa)); + return; + } + /* Passed so far! Determine nature of ARP packet */ + if (aa->arp_op == htons(ARPOP_REQUEST)) { + /* See if request is targetted at our IP address */ + memcpy((char *)&ent.at_iaddr, (char *)aa->arp_tpa, IP_ADRSIZ); + if (ent.at_iaddr.s_addr == ehost_ip.s_addr) { + /* Yep! Send back our reply! */ + arp_reply((unsigned char *)aa->arp_sha, + (unsigned char *)aa->arp_spa); + return; + } + if (DP_DBGFLG) { + char ipbuf[OSN_IPSTRSIZ]; + dbprintln("Dropped ARP req for %s", + ip_adrsprint(ipbuf, (unsigned char *)&ent.at_iaddr)); + } + return; + } + if (aa->arp_op != htons(ARPOP_REPLY)) { /* Check ARP type REPLY */ + if (DP_DBGFLG) + dbprintln("Dropped ARP, type %0x != (req | rep)", aa->arp_op); + return; + } + + /* Passed! Now extract resolved IP and EA from sender fields */ + memcpy((char *)&ent.at_iaddr, (char *)aa->arp_spa, IP_ADRSIZ); + memcpy((char *)&ent.at_eaddr, (char *)aa->arp_sha, ETHER_ADRSIZ); + + /* Now look up and determine if it's for an outstanding request of ours */ + at = arptab_look(ent.at_iaddr); + if (!at || (at->at_flags & ARPF_COM)) { + if (DP_DBGFLG) { + char ipbuf[OSN_IPSTRSIZ]; + dbprintln("Dropped ARP reply, %s IP %s", + (at ? "no req for" : "already have"), + ip_adrsprint(ipbuf, (unsigned char *)&ent.at_iaddr)); + } + return; + } + + /* Success! */ + arp_set(at, + at->at_iaddr, + &ent.at_eaddr, /* Remember new ether addr */ + at->at_flags | ARPF_COM); /* Say entry now complete */ + if (swstatus) { + char ipbuf[OSN_IPSTRSIZ]; + char eabuf[OSN_EASTRSIZ]; + dbprintln("ARP cached %s = %s", + ip_adrsprint( ipbuf, (unsigned char *)&ent.at_iaddr), + eth_adrsprint(eabuf, (unsigned char *)&ent.at_eaddr)); + } +} + + +/* ARP_REPLY - Send out an ARP Reply for ourselves + */ +void +arp_reply(unsigned char *eap, /* Requestor ether addr */ + unsigned char *iap) /* Requestor IP addr */ +{ + struct eth_header eh; + struct ether_arp arp; + + /* Build ethernet header */ + eh_dset(&eh, eap); /* Set dest addr */ + eh_sset(&eh, &ihost_ea); /* Set ether source addr */ + eh_tset(&eh, ETHERTYPE_ARP); + + /* Now put together the ARP packet */ + arp.arp_hrd = htons(ARPHRD_ETHER); /* Set hdw addr format */ + arp.arp_pro = htons(ETHERTYPE_IP); /* Set ptcl addr fmt */ + arp.arp_hln = sizeof(arp.arp_sha); /* Hdw address len */ + arp.arp_pln = sizeof(arp.arp_spa); /* Ptcl address len */ + arp.arp_op = htons(ARPOP_REPLY); /* Type REPLY */ + + memcpy((char *)arp.arp_spa, /* Sender IP addr */ + (char *)&ihost_ip, sizeof(arp.arp_sha)); + + /* Sender hdw addr and IP addr (that's us - the resolved info) */ + ea_set(arp.arp_sha, &ihost_ea); /* Sender hdw addr */ + memcpy((char *)arp.arp_spa, (char *)&ehost_ip, IP_ADRSIZ); + + /* Target hdw addr and IP addr (for politeness?) */ + ea_set(arp.arp_tha, eap); /* Target hdw addr */ + memcpy((char *)arp.arp_tpa, iap, IP_ADRSIZ); + + /* Now send it! */ + if (swstatus) { + char ipbuf[OSN_IPSTRSIZ]; + dbprintln("ARP reply sent to %s", + ip_adrsprint(ipbuf, iap)); + } + + ether_write(&eh, (unsigned char *)&arp, sizeof(arp)); +} +#endif /* !KLH10_NET_TUN */ + +/* IMPTOHOST - Child-process main loop for pumping packets from IMP to HOST. +** Reads packets from net, fragments if necessary, and feeds +** IMP packets to DP superior process. +*/ +#if KLH10_NET_BPF +# define MAXETHERLEN OSN_BPF_MTU +#else +# define MAXETHERLEN 1600 /* Actually 1519 but be generous */ +#endif + +#define NINBUFSIZ (DPIMP_DATAOFFSET+MAXETHERLEN) + +void +imptohost(register struct dpimp_s *dpimp) +{ + register struct dpx_s *dpx = dp_dpxfr(&dp); + register int cnt; + unsigned char *inibuf; + unsigned char *buffp; + size_t max; + int stoploop = 50; + + inibuf = dp_xsbuff(dpx, &max); /* Get initial buffer ptr */ + + /* Tell KLH10 we're initialized and ready by sending initial packet */ + dp_xswait(dpx); /* Wait until buff free, in case */ + dp_xsend(dpx, DPIMP_INIT, 0); /* Send INIT */ + + if (DBGFLG) + fprintf(stderr, "[dpimp-R: sent INIT]\r\n"); + +#if (KLH10_NET_NIT || KLH10_NET_DLPI || KLH10_NET_PFLT || \ + KLH10_NET_TUN || KLH10_NET_LNX) + for (;;) { + /* Make sure that buffer is free before clobbering it */ + dp_xswait(dpx); /* Wait until buff free */ + + if (DBGFLG) + fprintf(stderr, "[dpimp-R: InWait]\r\n"); + + /* Set up buffer and initialize offsets */ +#if KLH10_NET_TUN + /* XXX clean up TUN condits by using "0" ETHER_HDRSIZ substitute */ + buffp = inibuf + DPIMP_DATAOFFSET; +#else + buffp = inibuf + (DPIMP_DATAOFFSET - ETHER_HDRSIZ); +#endif + + /* OK, now do a blocking read on packetfilter input! */ + cnt = read(pffd, buffp, MAXETHERLEN); +#if KLH10_NET_TUN + if (cnt <= 0) { /* No ether headers on TUN */ +#else + if (cnt <= ETHER_HDRSIZ) { +#endif +#if KLH10_NET_PFLT + /* If call times out due to EIOCSRTIMEOUT, will return 0 */ + if (cnt == 0 && dpimp->dpimp_rdtmo) + continue; /* Just try again */ +#endif + if (DBGFLG) + fprintf(stderr, "[dpimp-R: ERead=%d, Err=%d]\r\n", + cnt, errno); + + if (cnt < 0 && (errno == EINTR)) /* Ignore spurious signals */ + continue; + + /* Error of some kind */ + fprintf(stderr, "[dpimp-R: Eread = %d, ", cnt); + if (cnt < 0) { + if (--stoploop <= 0) + efatal(1, "Too many retries, aborting]"); + fprintf(stderr, "errno %d = %s]\r\n", + errno, dp_strerror(errno)); + } else if (cnt > 0) + fprintf(stderr, "no ether data]\r\n"); + else fprintf(stderr, "no packet]\r\n"); + + continue; /* For now... */ + } + if (DBGFLG) { + if (DBGFLG & 0x4) { + fprintf(stderr, "\r\n[dpimp-R: Read=%d\r\n", cnt); + dumppkt(buffp, cnt); + fprintf(stderr, "]"); + } + else + fprintf(stderr, "[dpimp-R: Read=%d]", cnt); + } + + /* Have packet, now dispatch it to host */ +#if KLH10_NET_LNX + /* Linux has no packet filtering, thus must apply manual check to + each and every packet read, even if dedicated. + */ + if (!lnx_filter(dpimp, buffp, cnt)) + continue; /* Drop packet, continue reading */ + +#endif /* KLH10_NET_LNX */ +#if !KLH10_NET_TUN +#if 1 + /* Verify that pf filtering is doing its job */ + switch (eh_tget((struct eth_header *)buffp)) { + case ETHERTYPE_IP: + break; + case ETHERTYPE_ARP: /* If ARP, */ + arp_gotrep(buffp, cnt); /* attempt to process replies */ + continue; /* and always drop packet */ + default: + error("Non-IP ether packet: %0X", + eh_tget((struct eth_header *)buffp)); + continue; + } +#endif +#endif /* !KLH10_NET_TUN */ + + /* OK, it claims to be an IP packet, see if so long that we + ** need to fragment it. Yech! + */ +#if !KLH10_NET_TUN + cnt -= ETHER_HDRSIZ; + if (cnt > SI_MAXMSG) { + ihl_frag(cnt, buffp + ETHER_HDRSIZ); + } else { + /* Small enough to constitute one IMP message, so pass it on! */ + ihl_hhsend(dpimp, cnt, buffp + ETHER_HDRSIZ); + } +#else + if (cnt > SI_MAXMSG) + ihl_frag(cnt, buffp); + else + ihl_hhsend(dpimp, cnt, buffp); +#endif /* KLH10_NET_TUN */ + + } +#endif /* NIT || DLPI || PFLT || TUN || LNX */ + +#if KLH10_NET_BPF + for (;;) { + char *bp, *ep, *pp; + size_t caplen, hdrlen; + + /* Make sure that buffer is free before clobbering it */ + dp_xswait(dpx); /* Wait until buff free */ + + buffp = inibuf + DPIMP_DATAOFFSET; + + if ((cnt = read(pffd, buffp, OSN_BPF_MTU)) < 0) { + fprintf(stderr, "dpimp: BPF read = %d, ", cnt); + if (--stoploop <= 0) + efatal(1, "Too many retries, aborting"); + fprintf(stderr, "errno %d = %s]\r\n", errno, dp_strerror(errno)); + } + /* If call times out, will return 0 */ +/* XXX fix up like dpni20 */ + if (cnt == 0 /* && dpimp->dpimp_rdtmo */) + continue; /* Just try again */ + + if (DBGFLG) + dbprintln("BPF read = %d", cnt); + + /* Grovel through buffer, sending each packet. Note that + ** sending can prepend stuff onto data, which trashes the BPF header; + ** thus pointer to next header must be derived BEFORE each send. + ** The LHDH can also pad-trash the following 3 bytes if the data count + ** isn't a multiple of 4 -- hence need to preserve vals from next hdr! + */ + bp = buffp; ep = bp + cnt; +# define bhp(p) ((struct bpf_hdr *)(p)) + caplen = bhp(bp)->bh_caplen; /* Pre-fetch first BPF header */ + hdrlen = bhp(bp)->bh_hdrlen; + while (bp < ep) { + + cnt = caplen - ETHER_HDRSIZ; + pp = bp + hdrlen + ETHER_HDRSIZ; + + /* Point to next header now, before current one is trashed */ + bp += BPF_WORDALIGN(caplen + hdrlen); + if (bp < ep) { + caplen = bhp(bp)->bh_caplen; + hdrlen = bhp(bp)->bh_hdrlen; + } +# undef bhp + if (DBGFLG) + dbprintln("BPF pkt = %d", cnt); + + /* See if so long that we need to fragment it. Yech! */ + if (cnt > SI_MAXMSG) { + ihl_frag(cnt, pp); + } else { + /* Small enough for one IMP message, so pass it on! */ + ihl_hhsend(dpimp, cnt, pp); + } + + /* Wait until send ACKed, assume buff still OK */ + dp_xswait(dpx); + } + } +#endif /* KLH10_NET_BPF */ +} + +void +ihl_frag(int cnt, unsigned char *pp) +{ + /* For now, just drop it. */ + error("Too-large packet (%d), can't fragment yet", cnt); +} + + +/* Send regular message from IMP to HOST. +** One problem here is what value to put in as the source host/imp. +** All we have is the ethernet source address (the IP header source addr +** is not meaningful as it is that of the ultimate source, not the +** last gateway/router). +** One possibility is to query the native host's ARP tables to look +** up the IP address for that ethernet address, and translate that. +** Another tactic is to do the inverse of hi_iproute() by +** extracting the IP source addr and seeing if it's on our local net. +** If so, put it in the IMP leader, otherwise substitute our default +** gateway. +** Fastest punt would be to just use the IP address of the native host, +** as if it were the final gateway -- and in a sense it is! +*/ + +/* Buffer for I->H leader, must initialize with source-host at startup */ +unsigned char ihobuf[SIH_HSIZ+SI_LDRSIZ] = { +#if SIH_HSIZ + 0, 0, 0, 0, /* SIH_HDR, SIH_TDATA, 0, 0 */ +#endif + 017, 0, 0, 0, /* IMP->Host normal message */ + 0, 0, 0, 0, /* IMP->Host source host */ + SILNK_IP, 0, 0, 0 /* IMP->Host IP msg ID */ +}; + + +void +ihl_hhsend(register struct dpimp_s *dpimp, + int cnt, + register unsigned char *pp) + /* "pp" is packet data ptr, has room for header preceding */ +{ + register int bits = cnt * 8; /* Why not... msg length in bits */ + union ipaddr haddr; + + /* Set up IMP leader */ + ihobuf[SIH_HSIZ+SIL_LEN0] = bits & 0377; /* Lo 8 bits */ + ihobuf[SIH_HSIZ+SIL_LEN1] = (bits>>8) & 0377; /* Hi 8 bits */ + + /* Hack to set host/imp value as properly as possible. */ + memcpy((char *)&haddr.ia_octet[0], pp + IPBOFF_SRC, 4); + if ((haddr.ia_addr.s_addr & ihost_nm.s_addr) != ihost_net.s_addr) { + haddr.ia_addr = gwdef_ip; /* Not local, use default GW */ + } + + ihobuf[SIH_HSIZ+SIL_HST] = haddr.ia_octet[1]; + ihobuf[SIH_HSIZ+SIL_IMP1] = haddr.ia_octet[2]; + ihobuf[SIH_HSIZ+SIL_IMP0] = haddr.ia_octet[3]; + + cnt += SI_LDRSIZ; /* Compensate for IMP leader size */ +#if SIH_HSIZ + ihobuf[2] = (cnt >> 8) & 0377; /* High byte of count */ + ihobuf[3] = (cnt & 0377); /* Low byte of count */ +#endif + + pp -= sizeof(ihobuf); /* Back up to start of header+leader */ + cnt += SIH_HSIZ; + memcpy(pp, ihobuf, sizeof(ihobuf)); /* Prepend onto data */ + + /* Send up to host! Assume we're already in shared buffer. */ + { + register struct dpx_s *dpx = dp_dpxfr(&dp); + register unsigned char *buff; + size_t off, max; + + buff = dp_xsbuff(dpx, &max); /* Set up buffer ptr & max count */ + if ((off = pp - buff) >= max) { + efatal(1, "Bogus IH offset: %ld", (long)off); + } + dpimp->dpimp_inoff = off; /* Tell host what offset is */ + dp_xsend(dpx, DPIMP_RPKT, cnt+off); + + if (DBGFLG) + fprintf(stderr, "[dpimp-R: sent RPKT %d+%d]", (int)off, cnt); + } +} + +/* HOSTTOIMP - Parent main loop for pumping packets from HOST to IMP. +** Reads IMP message from DP superior +** and interprets it. If a regular message, bundles it up and +** outputs to NET. +*/ +void +hosttoimp(register struct dpimp_s *dpimp) +{ + register struct dpx_s *dpx = dp_dpxto(&dp); /* Get ptr to "To-DP" dpx */ + register unsigned char *buff; + size_t max; + register int rcnt; + unsigned char *inibuf; +#if !KLH10_NET_TUN + struct in_addr ipdest; +#endif + + inibuf = dp_xrbuff(dpx, &max); /* Get initial buffer ptr */ + + if (DBGFLG) + fprintf(stderr, "[dpimp-W: Starting loop]\r\n"); + + for (;;) { + if (DBGFLG) + fprintf(stderr, "[dpimp-W: CmdWait]\r\n"); + + /* Wait until 10 has a command for us */ + dp_xrwait(dpx); /* Wait until something there */ + + /* Process command from 10! */ + switch (dp_xrcmd(dpx)) { + + default: + fprintf(stderr, "[dpimp: Unknown cmd %d]\r\n", dp_xrcmd(dpx)); + dp_xrdone(dpx); + continue; + + case DPIMP_SPKT: /* Send regular packet */ + rcnt = dp_xrcnt(dpx); + + /* Adjust for offset */ + rcnt -= dpimp->dpimp_outoff; + buff = inibuf + dpimp->dpimp_outoff; + if (DBGFLG) { + if (DBGFLG & 0x2) { + fprintf(stderr, "\r\n[dpimp-W: Sending %d\r\n", rcnt); + dumppkt(buff, rcnt); + fprintf(stderr, "]"); + } + else + fprintf(stderr, "[dpimp-W: SPKT %d]", rcnt); + } + break; + } + + /* Come here to handle output packet */ + + if (rcnt < (SIH_HSIZ+SI_LDRSIZ)) { + error("Host-Imp message too small: %d", rcnt); + continue; + } +#if 1 + if (buff[SIH_HSIZ+SIL_FMT] != 017) { + error("Old-format leader received: %o", buff[SIH_HSIZ+SIL_FMT]); + continue; + } +#endif + /* Dispatch by message type. ITS actually only sends + ** NOP and HH messages, nothing else. + */ + switch (buff[SIH_HSIZ+SIL_TYP]) { + + case SIMT_HH: /* Regular Host-Host Message */ + if (buff[SIH_HSIZ+SIL_LNK] != SILNK_IP) { + error("Non-IP Host-Imp msg received: %#o", + buff[SIH_HSIZ+SIL_LNK]); + continue; + } +#if KLH10_NET_TUN + if (DBGFLG) + dbprintln("net out = %d", rcnt - (SIH_HSIZ+SI_LDRSIZ)); + if (write(pffd, &buff[SIH_HSIZ+SI_LDRSIZ], + rcnt - (SIH_HSIZ+SI_LDRSIZ)) < 0) + syserr(errno, "tun write() failed"); + else { +#else + if (hi_iproute(&ipdest, &buff[SIH_HSIZ], rcnt - SIH_HSIZ)) { + ip_write(&ipdest, &buff[SIH_HSIZ+SI_LDRSIZ], + rcnt - (SIH_HSIZ+SI_LDRSIZ)); +#endif +#if !SICONF_SIMP +# error "Too hard to implement non-Simple IMP model!" +# if 0 + { int res; + /* IP packet sent out to net, now send RFNM to host */ + buff[SIH_HSIZ+SIL_TYP] = SIMT_RFNM; + buff[2] = 0; /* High byte of count */ + buff[3] = SI_LDRSIZ; /* Low byte of count */ + res = write(ih_fd, buff, rcnt = SIH_HSIZ+SI_LDRSIZ); + if (res != rcnt) { + esfatal(2, "IH pipe write failed: %d != %d", res, rcnt); + } else if (swurgsig) + kill(cpupid, swurgsig); /* Wake host (cpu) up */ + } +# endif +#endif + } + break; + + case SIMT_LERR: /* Error in leader: err in previous IMP-to-Host ldr */ + error("Received Host-to-IMP leader error msg"); + break; + + case SIMT_GDWN: /* Host Going Down */ + error("Received Host-going-down msg"); + break; + + case SIMT_NOP: /* NOP */ + /* A real IMP would examine this to see how much padding to + ** add onto its leaders. However, ITS never wants any. + */ + break; + + case SIMT_DERR: /* Error in Data (has msg-id) */ + error("Received Host-to-IMP data error msg"); + break; + + default: + error("Unknown host-imp msg type: %#o", buff[SIH_HSIZ+SIL_TYP]); + } + + /* Command done, tell 10 we're done with it */ + if (DBGFLG) + fprintf(stderr, "[dpimp-W: CmdDone]"); + + dp_xrdone(dpx); + } +} + +#if !KLH10_NET_TUN + +/* HI_IPROUTE - Determine where to actually send Host-Host IP datagram. +** See discussion of routing in comments at start of file. + For now, let's build the IP address by slapping the 1st IP byte +on from the native host's IP address, and taking the rest from the host/imp +fields per scheme above. SIMP then checks to see if it's a local net +address, and if so uses that IP address. If not (ITS thinks it's local, +but it isn't on right subnet) then SIMP substitutes a single default +gateway address. This resolves some but not all of the problem. +*/ + +int +hi_iproute(struct in_addr *ipa, /* Dest IP addr to be put here */ + unsigned char *lp, /* Ptr to start of IMP-Host leader */ + int cnt) /* Cnt of data including leader */ +{ + union ipaddr haddr; + + if (cnt < (SI_LDRSIZ+IPBOFF_DEST+4)) { + error("Host-Imp IP datagram too short: %d", cnt); + return FALSE; + } + + /* Derive destination IP address from IMP leader */ + haddr.ia_addr = ihost_ip; /* Init with native IP addr */ + haddr.ia_octet[1] = lp[SIL_HST]; /* Set up host byte */ + haddr.ia_octet[2] = lp[SIL_IMP1]; /* High imp byte (old "logical host")*/ + haddr.ia_octet[3] = lp[SIL_IMP0]; /* Low byte of imp */ + + /* Now see if address is local, or if gateway routing needed. */ + if ((haddr.ia_addr.s_addr & ihost_nm.s_addr) == ihost_net.s_addr) { + *ipa = haddr.ia_addr; /* Local, win! */ + return TRUE; + } + + /* Yucko, need to substitute gateway address. Lotsa luck. */ + *ipa = gwdef_ip; + return TRUE; +} + + +/* IP_WRITE - Send IP packet out onto ethernet via packetfilter. +*/ + +void +ip_write(struct in_addr *ipa, unsigned char *buf, int len) +{ + struct eth_header eh; + + /* Set up ethernet header */ + if (!arp_look(*ipa, (struct ether_addr *)eh_dptr(&eh))) { + /* Failed, so generate an ARP request and send that instead. + ** Perhaps later add code to hang on to the packet; problem is figuring + ** out when it's time to try sending it again. + ** Could spin off thread to do timeout & re-send? + */ + arp_req(ipa); /* Send ARP request for this packet */ + sleep(1); /* Gross crock */ + if (!arp_look(*ipa, (struct ether_addr *)eh_dptr(&eh))) { + /* Try again */ + if (swstatus) { + char ipbuf[OSN_IPSTRSIZ]; + dbprintln("No ARP, dropped pkt to %s", + ip_adrsprint(ipbuf, (unsigned char *)ipa)); + } + return; /* Failed, just ignore for now */ + } + } + eh_sset(&eh, &ihost_ea); /* Set source addr */ + eh_tset(&eh, ETHERTYPE_IP); + + ether_write(&eh, buf, len); +} + + +void +ether_write(register struct eth_header *hp, + register unsigned char *pp, + register int cnt) +{ +#if KLH10_NET_NIT + struct strbuf ctl, dat; + struct sockaddr sa; + + /* First set up control message to specify destination, expressed as a + ** sockaddr. The interface output driver builds an ethernet header + ** from that information. + ** If sa_family is AF_UNSPEC, then sa_data is interpreted + ** as a ether_header (it just so happens to be the same length, + ** 14 bytes - bleah!) and the dest host is taken from ether_dhost, + ** plus type from ether_type. + ** If sa_family is AF_INET, then sa_data is interpreted as + ** the rest of a sockaddr_in, and ARP resolution is done on the sin_addr + ** field to find the correct destination ethernet addr. The type is always + ** set to ETHERTYPE_IP. + ** Unfortunately AF_INET cannot be used on NIT output + ** currently; only AF_UNSPEC is allowed. + */ + sa.sa_family = AF_UNSPEC; /* Copy ether header */ + memcpy(sa.sa_data, (char *)hp, ETHER_HDRSIZ); + + ctl.maxlen = ctl.len = sizeof(struct sockaddr); + ctl.buf = (char *)&sa; + dat.maxlen = dat.len = cnt; + dat.buf = (char *)pp; + + if (DP_DBGFLG) + dbprintln("net out = %d", cnt); + + if (putmsg(pffd, &ctl, &dat, 0) < 0) { + /* What to do here? For debugging, complain but return. */ + error("putmsg failed - %s", dp_strerror(errno)); + } + +#elif KLH10_NET_DLPI + struct strbuf ctl, dat; +# if DPIMP_DATAOFFSET /* New code, OK to simply prepend header */ + dat.buf = (char *)(pp - ETHER_HDRSIZ); + memcpy(dat.buf, (char *)hp, ETHER_HDRSIZ); +# else /* Old code, does extra buffer copy */ + unsigned char buf[MAXETHERLEN]; + + memcpy(buf, (char *)hp, ETHER_HDRSIZ); + memcpy(buf+ETHER_HDRSIZ, pp, cnt); + dat.buf = (char *)buf; +# endif + dat.maxlen = dat.len = (cnt + ETHER_HDRSIZ); + + if (DP_DBGFLG) + dbprintln("net out = %d", cnt); + + if (putmsg(pffd, NULL, &dat, 0) < 0) { + /* What to do here? For debugging, complain but return. */ + error("putmsg failed - %s", dp_strerror(errno)); + } +#elif KLH10_NET_PFLT + /* The lossage is endless... on DEC OSF/1 packetfilter FDs, + ** writev() *WILL NOT WORK*. It appears to succeed, but nothing + ** ever shows up on the output! + */ +# if DPIMP_DATAOFFSET /* New code, OK to simply prepend header */ + char *buf = (char *)(pp - ETHER_HDRSIZ); + memcpy(buf, (char *)hp, ETHER_HDRSIZ); +# else /* Old code, does extra buffer copy */ + unsigned char buf[MAXETHERLEN]; + memcpy(buf, (char *)hp, ETHER_HDRSIZ); + memcpy(buf+ETHER_HDRSIZ, pp, cnt); +# endif + + if (DP_DBGFLG) + dbprintln("net out = %d", cnt); + + if (write(pffd, buf, (size_t)(cnt + ETHER_HDRSIZ)) < 0) { + /* What to do here? For debugging, complain but return. */ + error("write failed - %s", dp_strerror(errno)); + } + +#elif KLH10_NET_BPF || KLH10_NET_LNX + struct iovec iov[2]; + + iov[0].iov_base = (char *) hp; + iov[0].iov_len = ETHER_HDRSIZ; + iov[1].iov_base = pp; + iov[1].iov_len = cnt; + + if (DP_DBGFLG) + dbprintln("net out = %d", cnt); + + if (writev(pffd, iov, sizeof(iov)/sizeof(*iov)) < 0) { + /* What to do here? For debugging, complain but return. */ + error("writev() failed - %s", dp_strerror(errno)); + } +#elif KLH10_NET_TUN + /* No code needed here -- routine never used */ +#else +# error "No implementation for ether_write()" +#endif +} + +#endif /* !KLH10_NET_TUN */ + +void +dumppkt(unsigned char *ucp, int cnt) +{ + int i; + + while (cnt > 0) { + for (i = 8; --i >= 0 && cnt > 0;) { + if (--cnt >= 0) + fprintf(stderr, " %02x", *ucp++); + if (--cnt >= 0) + fprintf(stderr, "%02x", *ucp++); + } + fprintf(stderr, "\r\n"); + } +} + +/* Add OSDNET shared code here */ + +#include "osdnet.c" diff --git a/src/dpimp.h b/src/dpimp.h new file mode 100644 index 0000000..dcd6459 --- /dev/null +++ b/src/dpimp.h @@ -0,0 +1,185 @@ +/* DPIMP.H - Definitions for IMP emulator +*/ +/* $Id: dpimp.h,v 2.4 2001/11/19 10:45:49 klh Exp $ +*/ +/* Copyright © 1992, 1998, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dpimp.h,v $ + * Revision 2.4 2001/11/19 10:45:49 klh + * Add blob into dpimp_s for ARP cache. + * + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#ifndef DPIMP_INCLUDED +#define DPIMP_INCLUDED 1 + +#ifdef RCSID + RCSID(dpimp_h,"$Id: dpimp.h,v 2.4 2001/11/19 10:45:49 klh Exp $") +#endif + + +#ifndef SICONF_SIMP +# define SICONF_SIMP 1 /* TRUE if compiling Simple-IMP only, not true IMP */ +#endif + +/* IMP leader definitions */ + +/* Byte offsets in IMP leader */ +#define SIL_FMT 0 /* Always 0000 + 1111 (new 96-bit format type) */ + /* Else low 4 bits are old msg type (4 = nop) */ +#define SIL_NET 1 /* Net number (always 0) */ +#define SIL_FLG 2 /* Flags (0363 unused, 010 trace (ignored), */ + /* and 04 shd be ignored) */ +#define SIL_TYP 3 /* Message type */ +#define SIL_HTY 4 /* Handling type */ + /* (7=big buffs, 4=small buffs, 0=ctl link) */ +#define SIL_HST 5 /* Host number on IMP */ +#define SIL_IMP1 6 /* IMP number (high 8 bits) */ +#define SIL_IMP0 7 /* " " ( low 8 bits) */ +#define SIL_LNK 8 /* Link number (High 8 bits of Message ID) */ +#define SIL_SUB 9 /* Low 4 bits of MsgID (0), then 4 bits of Sub-type */ +#define SIL_LEN1 10 /* Message length (high 8 bits) */ +#define SIL_LEN0 11 /* " " ( low 8 bits) */ + +#define SI_LDRSIZ 12 /* # bytes in IMP leader */ + /* Remaining bytes are data, specifically IP datagram */ + +#define SILNK_IP 0233 /* Link # to use for IP datagram messages */ + +/* IMP message types (IMP -> Host) */ + +#define SIMT_HH 0 /* Regular Host-Host Message */ +#define SIMT_LERR 1 /* Error in Leader (no msg-id) */ +#define SIMT_GDWN 2 /* IMP Going Down */ +#define SIMT_UN3 3 /* - */ +#define SIMT_NOP 4 /* NOP */ +#define SIMT_RFNM 5 /* RFNM - Ready For Next Message (xmit succeeded) */ +#define SIMT_HDS 6 /* Host Dead Status (general info) */ +#define SIMT_DHD 7 /* Destination Host Dead (xmit failed) */ +#define SIMT_DERR 8 /* Error in Data (has msg-id) */ +#define SIMT_INC 9 /* Incomplete Transmission (xmit failed temporarily) */ +#define SIMT_IRS 10 /* Interface Reset - IMP dropped its ready line */ + +/* In message types 2 and 6, the going-down status 16-bit word is + stored in (SIL_LNK<<8 + SIL_SUB). + + In message type 4 (NOP) the padding count is the low 4 bits of SIL_SUB. + Padding is only put on type-0 messages to separate the leader from + the data. +*/ + +#define SI_MAXMSG 1007 /* Max # of data bytes in an IMP regular message */ + +#if KLH10_DEV_DPIMP /* Standard DP version */ + +#ifndef DPSUP_INCLUDED +# include "dpsup.h" +#endif + +/* Version of DPIMP-specific shared memory structure */ + +#define DPIMP_VERSION ((1<<10) | (1<<5) | (2)) /* 1.1.2 */ + +/* DPIMP-specific stuff */ + /* C = controlling parent sets, D = Device proc sets */ + /* If both, 1st letter indicates inital setter */ +struct dpimp_s { + struct dpc_s dpimp_dpc; /* CD Standard DPC portion */ + int dpimp_ver; /* C Version of shared struct */ + int dpimp_attrs; /* C Attribute flags */ + char dpimp_ifnam[16]; /* CD Interface name if any */ + unsigned char dpimp_eth[6]; /* CD Ethernet address of interface */ + unsigned char dpimp_ip[4]; /* C 10's IP address to filter on, if shared */ + unsigned char dpimp_gw[4]; /* C Default GW address for IMP to use */ + int dpimp_inoff; /* C Offset in buffer of input (I->H) data */ + int dpimp_outoff; /* D Offset in buffer of output (H->I) data */ + int dpimp_backlog; /* C Max sys backlog of rcvd packets */ + int dpimp_dedic; /* C TRUE if dedicated ifc, else shared */ + int dpimp_doarp; /* C TRUE to do ARP hackery, if shared */ + int dpimp_rdtmo; /* C # secs to timeout on packetfilter read */ + + /* The following is not used by the controlling parent at all; it + is only used by DPIMP's two in/out procs. It is here instead of in a + separate shared segment for simplicity, and is an unstructured + blob to shield the controller from DPIMP implementation details. + */ +#ifndef DPIMP_BLOB_SIZE +# define DPIMP_BLOB_SIZE 2000 +#endif + size_t dpimp_blobsiz; + unsigned char dpimp_blob[DPIMP_BLOB_SIZE]; +}; + +/* Buffer offset: + In order to eliminate the need to copy bytes from one buffer +into another simply to add or subtract an ethernet header or IMP +leader, I/O is actually performed at an OFFSET into the shared memory +buffers. This offset value (dpimp_inoff or dpimp_outoff) is set by +the buffer writer and is made large enough to accomodate the largest +header/leader that other code will need to prefix onto the data. The +data count includes this offset; to access the data, the buffer reader +must use the offset (provided by the writer) to both increment the +initial pointer and decrement the count. + + Currently IMP leaders are 12 bytes and ethernet headers are 14 +bytes, so the initial offset is rounded up to 16 bytes (could be 14 +but the alignment may make it slightly easier for O/S I/O). + +*/ +#define SIH_HSIZ 0 /* No bytes in SIMP header */ +#define DPIMP_DATAOFFSET (16) /* max(leader,etherhdr) rounded up */ + +/* Commands to and from DP and KLH10 LHDH driver */ + + /* From 10 to DP */ +#define DPIMP_RESET 0 /* Reset DP */ +#define DPIMP_SPKT 1 /* Send data packet to ethernet */ +#define DPIMP_SETETH 2 /* Set hardware ethernet address */ + + /* From DP to 10 */ +#define DPIMP_INIT 1 /* DP->10 Finished init */ +#define DPIMP_RPKT 2 /* DP->10 Received data packet from net */ + + /* Feature bits in dpimp_doarp */ +/* 0x1 */ /* TRUE -- translate into 0xF */ +#define DPIMP_ARPF_PUBL 0x2 /* Register self, publish */ +#define DPIMP_ARPF_PERM 0x4 /* Register self, permanent */ +#define DPIMP_ARPF_OCHK 0x8 /* Check outgoing ARPs for host platform */ + +#endif /* KLH10_DEV_DPIMP */ /* Standard DP version */ + +#if KLH10_DEV_SIMP /* Pipe version */ + +/* All communication between the SIMP and its owner takes place over a +** bidirectional byte stream, normally a pipe. Each direction is +** completely independent of the other. All data is sent as encapsulated +** packets with the following 4-byte header: +** Header Check - always 0354 (MagiC #) +** Packet Type - a SIH_Txxx value +** High byte of packet length +** Low byte of " " +*/ + +#define SIH_HDR 0354 /* Magic byte value introducing 4-byte header */ +#define SIH_TDATA 'm' /* Basic message to and from IMP */ +#define SIH_TSUIC 'd' /* To IMP: drop dead nicely */ +#define SIH_HSIZ 4 /* # bytes in header */ + +#endif /* KLH10_DEV_SIMP */ /* Pipe version */ + +#endif /* ifndef DPIMP_INCLUDED */ diff --git a/src/dpni20.c b/src/dpni20.c new file mode 100644 index 0000000..cb1c589 --- /dev/null +++ b/src/dpni20.c @@ -0,0 +1,1682 @@ +/* DPNI20.C - Device sub-Process for KLH10 NIA20 Ethernet Interface +*/ +/* $Id: dpni20.c,v 2.5 2001/11/19 10:36:00 klh Exp $ +*/ +/* Copyright © 1994, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dpni20.c,v $ + * Revision 2.5 2001/11/19 10:36:00 klh + * Solaris port: trivial cast fixes. + * + * Revision 2.4 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* + This is a program intended to be run as a SU child of the KLH10 +PDP-10 emulator, in order to provide ethernet interface I/O without +requiring that the KLH10 process itself run as superuser. + + There are actually two threads active in this process, one +which pumps packets from the network to the KLH10 and another that +pumps in the opposite direction from the KLH10 to the network. +Generally they are completely independent. + + ----------------------------- + +Useful pieces of documentation are in the following man pages: + FreeBSD: netintro(4) - General net interface ioctls + FreeBSD: inet(9) - Kernel network internals + FreeBSD: /usr/src/ - Actual source (both kernel & user) + + OSF/1: netintro(7) - General net interface ioctls + Interface addr/config SIOC{*}IF{*} + OSF/1: ln(7) - Describes ioctls for Lance ethernet interface, to: + Read & change phys addr SIOC{R,S}PHYSADDR + Add & delete multicast addrs SIOC{ADD,DEL}MULTI + Read & read/clear counters SIOCRD{Z}CTRS + Enable/disable loopback mode SIOC{EN,DIS}ABLBACK + OSF/1: arp(7) - Ioctls for managing ARP tables: + Set, Get, Delete ARP entry SIOC{S,G,D}ARP + OSF/1: bpf(7) - Berkeley Packet Filter + OSF/1: packetfilter(7) - BSD/Stanford packet filter + + SUN: intro(4), arp(4P), inet(4f), routing(4n) - General + lo(4n), ie(4s), if(4n), - Interface + nit(4p), nit_buf(4m), nit_if(4m), nit_pf(4m) - NIT packetfilter + +Useful auxiliary programs: + OSF/1: pfconfig(8) - Sets system packetfilter config values + OSF/1: pfstat(1) - Shows system packetfilter status & counts + gen: netstat(1) - General net interface status + gen: arp(8) - Shows/sets ARP tables + gen: ifconfig(8) - Shows/sets interface config stuff +*/ + +/* + Some notes on configuration flags + +The following configurations are allowed for: + + +[A] Shared net interface (DEDIC=FALSE, default) + IFC= (dpni_ifnam) specifies which interface to use. Optional. + ENADDR= (dpni_eth) cannot be set. + IPADDR= may be set. If so, filters packets for that IP address. + DECNET= may be set. If so, all DECNET packet types are added to + the filter; otherwise they are ignored. The host platform + must not itself use DECNET packets!!! + DOARP=FALSE + +[B] Dedicated net interface (DEDIC=TRUE) + Virtual 20 receives all packets addressed to its hardware interface, + including broadcast and multicast packets, of all types. + Promiscuous mode may be toggled. + (Although on Solaris, promiscuous mode is always necessary in order to + read packets of all ethernet types, rather than just one!) + + IFC= (dpni_ifnam) specifies which interface to use. Optional, + but *highly* recommended! + ENADDR= (dpni_eth) is used to set the ethernet address, if possible. + Optional. + DECNET= is ignored. + + [B.1] No funny stuff (DOARP=FALSE, default) + IPADDR= isn't needed and is ignored. + + [B.2] Hack ARP anyway (DOARP=TRUE) + Needed because host platform (Solaris) has problems + understanding TOPS-20 replies -- and TOPS-20 itself sometimes + corrupts its own replies! + IPADDR= must be specified!! + +For all configurations: + BACKLOG= (OSF/1 only) + RDTMO= (OSF/1 and BPF only) + +ARP behavior is selected by a combination of DOARP= plus other things. +The following general situations are possible: + + - The 20 doesn't need to see or send ARPs (TOPS monitor not using IP). + If ifc dedicated, or no ipaddr, then no filtering is done + nor any special help. + + - Ensure that 20 can see and send ARP packets (TOPS monitor using IP). + If shared ifc: do ARP filtering, special help optional. + If dedicated ifc: no filtering, special help optional. + + - Special help for the 20 if using IP: + + DOARP=2 (1) Register ARP info as "publish" with local platform, so + platform proxy-answers requests for the 20's address. + Necessary because it looks like TOPS-20 screws up + ARP replies sometimes! + (Also needed on Solaris and OSF/1 because this way + the packetfilter only needs to pass ARP replies, not + ARP requests). + DOARP=4 (2) Register ARP info as "permanent" with local platform, so + platform knows address itself. + Necessary because OSF/1 loopback omits ARP packets. + (May not need on Solaris?) + DOARP=8 (3) Watch for outgoing ARP reqs for local platform's address, + and simulate reply. + Same reason as (1) -- OSF/1 loopback omits ARP + packets, so platform won't see request! + (Unsure yet if Solaris needs this) +*/ + +#include +#include +#include +#include +#include + +#include "klh10.h" /* For config params */ + +/* This must precede any other OSD includes to ensure that DECOSF gets + the right flavor sockaddr (sigh) +*/ +#include "osdnet.h" /* OSD net defs, shared with DPIMP */ + +#include /* For setpriority() */ +#include /* For mlockall() */ + +#include "dpsup.h" /* General DP defs */ +#include "dpni20.h" /* NI20 specific defs */ + +#ifdef RCSID + RCSID(dpni20_c,"$Id: dpni20.c,v 2.5 2001/11/19 10:36:00 klh Exp $") +#endif + +/* Globals */ + +int chpid; /* PID of child, handles input (net-to-10). */ +int swstatus = TRUE; +int pffd; /* Packet-Filter FD (bidirectional) */ +struct dp_s dp; /* Device-Process struct for DP ops */ + +struct in_addr ehost_ip; /* Emulated host IP addr, net order */ +struct in_addr ihost_ip; /* Native host's IP addr, net order */ +struct ether_addr ihost_ea; /* Native host ether addr for selected ifc */ + +/* Debug flag reference. Use DBGFLG within functions that have "dpni"; + * all others must use DP_DBGFLG. Both refer to the same location. + * Caching a local copy of the debug flag is a no-no because it lives + * in a shared memory location that may change at any time. + */ +#define DBGFLG (dpni->dpni_dpc.dpc_debug) +#define DP_DBGFLG (((struct dpni20_s *)dp.dp_adr)->dpni_dpc.dpc_debug) + +int nmcats = 0; +unsigned char ethmcat[DPNI_MCAT_SIZ][6]; /* Table of known MCAT addresses */ + +/* Local predeclarations */ + +void ethtoten(struct dpni20_s *); +void tentoeth(struct dpni20_s *); + +void net_init(struct dpni20_s *dpni); +void eth_mcatset(struct dpni20_s *dpni); +void eth_adrset(struct dpni20_s *dpni); +void dumppkt(unsigned char *ucp, int cnt); +int arp_myreply(unsigned char *buf, int cnt); + +/* Error and diagnostic output */ + +static const char progname_i[] = "dpni20"; +static const char progname_r[] = "dpni20-R"; +static const char progname_w[] = "dpni20-W"; +static const char *progname = progname_i; + +static void efatal(int num, char *fmt, ...) +{ + fprintf(stderr, "\n[%s: Fatal error: ", progname); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fputs("]\r\n", stderr); + + /* DP automatically kills any child as well. */ + dp_exit(&dp, num); +} + +static void esfatal(int num, char *fmt, ...) +{ + fprintf(stderr, "\n[%s: ", progname); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fprintf(stderr, " - %s]\r\n", dp_strerror(errno)); + + /* DP automatically kills any child as well. */ + dp_exit(&dp, num); +} + +static void dbprint(char *fmt, ...) +{ + fprintf(stderr, "[%s: ", progname); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fputs("]", stderr); +} + +static void dbprintln(char *fmt, ...) +{ + fprintf(stderr, "[%s: ", progname); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fputs("]\r\n", stderr); +} + +static void error(char *fmt, ...) +{ + fprintf(stderr, "\n[%s: ", progname); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fputs("]\r\n", stderr); +} + +static void syserr(int num, char *fmt, ...) +{ + fprintf(stderr, "\n[%s: ", progname); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fprintf(stderr, " - %s]\r\n", dp_strerror(num)); +} + +int initdebug = 0; + +int +main(int argc, char **argv) +{ + register struct dpni20_s *dpni; + + /* Search for a "-debug" command-line argument so that we can start + debug output ASAP if necessary. + */ + if (argc > 1) { + int i; + for (i = 1; i < argc; ++i) { + if (strcmp(argv[i], "-debug") == 0) { + initdebug = TRUE; + break; + } + } + } + if (initdebug) + dbprint("Starting"); + + /* Right off the bat attempt to get the highest scheduling priority + ** we can. It's important that the NI respond as quickly as possible + ** or the 10 monitor is apt to declare it dead. + ** This applies primarily to the 10->NI process, not the NI->10 + ** which is a child of this one. + */ +#if 0 /* was CENV_SYS_SOLARIS */ + if (nice(-20) == -1) + syserr(errno, "Warning - cannot set high priority"); +#elif CENV_SYS_SOLARIS || CENV_SYS_DECOSF || CENV_SYS_XBSD || CENV_SYS_LINUX + if (setpriority(PRIO_PROCESS, 0, -20) < 0) + syserr(errno, "Warning - cannot set high priority"); +#endif + + /* Next priority is to quickly close the vulnerability window; + disable TTY cruft to ensure that any TTY hacking done by superior + process doesn't inadvertently kill us off. + */ + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + + if (initdebug) + dbprint("Started"); + + /* General initialization */ + if (geteuid() != 0) + efatal(1, "Must be superuser!"); + + if (!dp_main(&dp, argc, argv)) { + efatal(1, "DP init failed!"); + } + dpni = (struct dpni20_s *)dp.dp_adr; /* Make for easier refs */ + + /* Now can access DP args! + From here on we can use DBGFLG, which is actually a shared + memory reference that dpni points to. Check here to accomodate the + case where it's not already set but "-debug" was given as a command + arg; leave it alone if already set since the exact bits have + significance. + */ + if (initdebug && !DBGFLG) + DBGFLG = 1; + if (DBGFLG) + dbprint("DP inited"); + + /* Always attempt to lock memory since the DP processes are fairly + ** small, must respond quickly, and SU mode is more or less guaranteed. + ** Skip it only if dp_main() already did it for us. + */ +#if CENV_SYS_DECOSF || CENV_SYS_SOLARIS || CENV_SYS_LINUX + if (!(dpni->dpni_dpc.dpc_flags & DPCF_MEMLOCK)) { + if (mlockall(MCL_CURRENT|MCL_FUTURE) != 0) { + dbprintln("Warning - cannot lock memory"); + } + } +#endif + + /* Now set up variables based on parameters passed through + shared DP area. + */ + + /* If no IP address specified, ignore ARP hackery flag */ + if (memcmp(dpni->dpni_ip, "\0\0\0\0", 4) == 0) + dpni->dpni_doarp = FALSE; + + /* Canonicalize ARP hackery flags */ + if (dpni->dpni_doarp == TRUE) /* If simply set to "true", */ + dpni->dpni_doarp = DPNI_ARPF_PUBL | /* then do all */ + DPNI_ARPF_PERM | DPNI_ARPF_OCHK; + + /* Initialize network stuff including packet filter */ + net_init(dpni); + + /* Make this a status (rather than debug) printout? */ + if (DBGFLG) { + char sbuf[OSN_IPSTRSIZ+OSN_EASTRSIZ]; /* Lazily ensure big enough */ + + dbprintln("ifc \"%s\" => ether %s", + dpni->dpni_ifnam, + eth_adrsprint(sbuf, (unsigned char *)&ihost_ea)); + dbprintln(" addr %s", + ip_adrsprint(sbuf, (unsigned char *)&ihost_ip)); + dbprintln(" VHOST %s", + ip_adrsprint(sbuf, (unsigned char *)&ehost_ip)); + } + + /* If ARP hackery desired/needed, set up ARP entry so emulator host + ** kernel knows about our IP address (and can respond to ARP requests + ** for it, although this probably isn't necessary if the virtual 20's + ** monitor can do it). + */ + if (dpni->dpni_doarp & (DPNI_ARPF_PUBL|DPNI_ARPF_PERM)) { + if (!osn_arp_stuff(&dpni->dpni_ip[0], /* Set up fake IP addr */ + &dpni->dpni_eth[0], /* mapped to this ether addr */ + (dpni->dpni_doarp & DPNI_ARPF_PUBL))) /* Publicized if nec */ + esfatal(1, "ARP_STUFF failed"); + } + + /* Now start up a child process to handle input */ + if (DBGFLG) + dbprint("Forking R process"); + if ((chpid = fork()) < 0) + esfatal(1, "fork failed"); + if (chpid == 0) { + /* Child process. + ** Child inherits signal handlers, which is what we want here. + */ + + /* Fix up xfer mechanism so ACK of DP input goes to correct proc */ + dp.dp_adr->dpc_frdp.dpx_donpid = getpid(); + + /* And ensure its memory is locked too, since the lockage isn't + ** inherited over a fork(). Don't bother warning if it fails. + */ +#if CENV_SYS_DECOSF || CENV_SYS_SOLARIS || CENV_SYS_LINUX + (void) mlockall(MCL_CURRENT|MCL_FUTURE); +#endif + progname = progname_r; /* Reset progname to indicate identity */ + ethtoten(dpni); /* Child process handles input from net */ + } + progname = progname_w; /* Reset progname to indicate identity */ + tentoeth(dpni); /* Parent process handles output to net */ + + return 1; /* Never returns, but placate compiler */ +} + +/* NET_INIT - Initialize net-related variables, +** given network interface we'll use. +*/ +void net_init(register struct dpni20_s *dpni) +{ + struct ifreq ifr; + + /* Get the IP address we need to filter on, if shared */ + memcpy((char *)&ehost_ip, (char *)&dpni->dpni_ip, 4); + + /* Ensure network device name, if specified, isn't too long */ + if (dpni->dpni_ifnam[0] && (strlen(dpni->dpni_ifnam) + >= sizeof(ifr.ifr_name))) { + esfatal(0, "interface name \"%s\" too long - max %d", + dpni->dpni_ifnam, (int)sizeof(ifr.ifr_name)); + } + + /* Determine network device to use, if none was specified (this only + ** works for shared devices, as dedicated ones will be "down" and + ** cannot be found by iftab_init). + ** Also grab native IP and ethernet addresses, if ARP might need them. + */ + if ((!dpni->dpni_ifnam[0] && !dpni->dpni_dedic) + || (dpni->dpni_doarp & DPNI_ARPF_OCHK)) { + if (osn_iftab_init(IFTAB_IPS) <= 0) + esfatal(0, "Couldn't find interface information"); + + /* Found at least one! Pick first one, if a default is needed. */ + if (!dpni->dpni_ifnam[0]) { + struct ifent *ife = osn_ipdefault(); + if (!ife) + esfatal(0, "Couldn't find default interface"); + if (strlen(ife->ife_name) >= sizeof(dpni->dpni_ifnam)) + esfatal(0, "Default interface name \"%s\" too long, max %d", + ife->ife_name, (int)sizeof(dpni->dpni_ifnam)); + + strcpy(dpni->dpni_ifnam, ife->ife_name); + if (swstatus) + dbprintln("Using default interface \"%s\"", dpni->dpni_ifnam); + } + } + + /* Now set remaining stuff */ + + /* Set up packet filter. This also returns in "ihost_ea" + the ethernet address for the selected interface. + */ + { + struct osnpf npf; + + npf.osnpf_ifnam = dpni->dpni_ifnam; + npf.osnpf_dedic = dpni->dpni_dedic; + npf.osnpf_rdtmo = dpni->dpni_rdtmo; + npf.osnpf_backlog = dpni->dpni_backlog; + npf.osnpf_ip.ia_addr = ehost_ip; + /* Ether addr is both a potential arg and a returned value; + the packetfilter open may use and/or change it. + */ + ea_set(&npf.osnpf_ea, dpni->dpni_eth); /* Set requested ea if any */ + pffd = osn_pfinit(&npf, (void *)dpni); /* Will abort if fails */ + ea_set(&ihost_ea, &npf.osnpf_ea); /* Copy actual ea */ + } + + /* Now set any return info values in shared struct. + */ + memcpy(dpni->dpni_eth, (char *)&ihost_ea, 6); /* Copy ether addr */ + + if (DBGFLG) + dbprint("PF inited"); +} + +/* Packet Filter setup. +** No filter at all is used if the interface is dedicated; we see +** everything received on it. +** +** However, the packet filter for a SHARED interface must implement at least +** the following test: +** +** if ((dest IP addr == our IP addr && ethertype == IP) +** || (broadcast-mcast bit set)) +** { success; } +** +** For efficiency, the strictest test is done first -- the low shortword +** of the IP address, which is the most likely to differ from that of the +** true host. We do this even before checking whether the packet contains +** IP data; if it's too short, it's rejected anyway. +*/ + +/* Common packetfilter definitions - for all but BPF */ + +#if KLH10_NET_PFLT || KLH10_NET_NIT || KLH10_NET_DLPI + +#if KLH10_NET_PFLT +# define OSN_PFSTRUCT enfilter +# define PF_PRIO enf_Priority +# define PF_FLEN enf_FilterLen +# define PF_FILT enf_Filter +#elif (KLH10_NET_DLPI || KLH10_NET_NIT) +# define OSN_PFSTRUCT packetfilt +# define PF_PRIO Pf_Priority +# define PF_FLEN Pf_FilterLen +# define PF_FILT Pf_Filter +#endif + +struct OSN_PFSTRUCT pfilter; + +static void pfshow(struct OSN_PFSTRUCT *); + +struct OSN_PFSTRUCT * +pfbuild(void *arg, struct in_addr *ipa) +{ + register struct dpni20_s *dpni = (struct dpni20_s *)arg; + register unsigned short *p; + register unsigned char *ucp = (unsigned char *)ipa; + register struct OSN_PFSTRUCT *pfp = &pfilter; + + p = pfp->PF_FILT; /* Get addr of filter (length ENMAXFILTERS) */ + + /* First check for broadcast/multicast bit in dest address */ + *p++ = ENF_PUSHWORD + PKSWOFF_EDEST; +#ifdef ENF_PUSHONE /* New feature */ + if (htons(0x0100) == 01) { + *p++ = ENF_PUSHONE | ENF_AND; /* Do AND of multicast bit */ + *p++ = ENF_PUSHONE | ENF_COR; /* Succeed immediately if AND won */ + } else +#endif + { + *p++ = ENF_PUSHLIT | ENF_AND; /* Do AND of multicast bit */ + *p++ = htons(0x0100); + *p++ = ENF_PUSHLIT | ENF_COR; /* Succeed immediately if AND won */ + *p++ = htons(0x0100); + } + + /* Possibly insert check for DECNET protocol types. + ** Doing this check is inefficient if most of the traffic is IP. + ** Hopefully if the user asked for it, it's used a lot. + ** The following are the known types: + ** 6001 DNA/MOP + ** 6002 RmtCon + ** 6003 DECnet + ** 6004 LAT + ** 6016 ANF-10 (T10 only; not DECNET) + ** 9000 Loopback (?) + ** + ** For the time being, filtering is done by testing for + ** (type & ~0x001F) == 0x6000 + ** which accepts all types in the range 6000-601F inclusive. + ** 9000 is ignored. + */ + if (dpni->dpni_decnet) { +#if 0 + /* Blunt instrument approach */ + *p++ = ENF_PUSHWORD + PKSWOFF_ETYPE; /* Check for DECNET packet */ + *p++ = ENF_PUSHLIT | ENF_COR; + *p++ = htons(ETHERTYPE_DECnet); /* Win if 0x6003 */ + *p++ = ENF_PUSHWORD + PKSWOFF_ETYPE; /* Check for LAT packet */ + *p++ = ENF_PUSHLIT | ENF_COR; + *p++ = htons(ETHERTYPE_LAT); /* Win if 0x6004 */ + *p++ = ENF_PUSHWORD + PKSWOFF_ETYPE; /* Check for 0x6016 */ + *p++ = ENF_PUSHLIT | ENF_COR; + *p++ = htons(0x6016); + *p++ = ENF_PUSHWORD + PKSWOFF_ETYPE; /* Check for 0x6001 */ + *p++ = ENF_PUSHLIT | ENF_COR; + *p++ = htons(0x6001); + *p++ = ENF_PUSHWORD + PKSWOFF_ETYPE; /* Check for 0x6002 */ + *p++ = ENF_PUSHLIT | ENF_COR; + *p++ = htons(0x6002); +#else + /* Slightly faster, although sloppier */ + *p++ = ENF_PUSHWORD + PKSWOFF_ETYPE; /* Get ethernet type */ + *p++ = ENF_PUSHLIT | ENF_AND; + *p++ = htons(0xFFE0); /* Mask out ~0x001F */ + *p++ = ENF_PUSHLIT | ENF_COR; /* Succeed if result 6000 */ + *p++ = htons(0x6000); +#endif + } + + /* Test for an IEEE 802.3 packet with a specific dst/src LSAP. + Packet is 802.3 if type field is actually packet length -- in which + case it will be 1 <= len <= 1500 (note 1514 is max, of which header + uses 6+6+2=14). + There's seemingly no way to tell what order the ENF_LT, etc operands + are used in, so until that's established, use a simple masking + method. 1500 = 0x5dc, so use mask of 0xF800. + + Dst/src LSAPs are in next two bytes (1st shortwd after len). + */ + if (dpni->dpni_attrs & DPNI20F_LSAP) { + unsigned short lsaps = dpni->dpni_lsap; + + if (lsaps <= 0xFF) { /* If only one byte set, */ + lsaps |= (lsaps << 8); /* double it up for both dest & src */ + } + + *p++ = ENF_PUSHWORD + PKSWOFF_ETYPE; /* Get ethernet type field */ + *p++ = ENF_PUSHLIT | ENF_AND; + *p++ = htons(0xF800); /* Unused len bits shd be 0 */ + + /* Element on stack is now 0 if 802.3. Check LSAPs. */ + *p++ = ENF_PUSHWORD + PKSWOFF_SAPS; /* Get DSAP/SSAP word */ + *p++ = ENF_PUSHLIT | ENF_NEQ; /* Compare, set 0 if same */ + *p++ = htons(lsaps); + + /* Now compare result of both checks. + If both succeeded, both will be 0, otherwise some bits will be set. + */ + *p++ = ENF_OR; /* IOR the results together */ + *p++ = ENF_PUSHZERO | ENF_COR; /* Succeed if result 0 */ + } + + /* See if we're interested in IP (and thus ARP) packets. + This is assumed to be the LAST part of the filter, thus + it must either leave the correct result on the stack, or + ensure it is empty (if accepting the packet). + */ + if (memcmp(dpni->dpni_ip, "\0\0\0\0", 4) != 0) { + + /* Want to pass ARP replies as well, so 10 can see responses to any + ** ARPs it sends out. + ** NOTE!!! ARP *requests* are not passed! The assumption is that + ** osn_arp_stuff() will have ensured that the host platform + ** proxy-answers requests for our IP address. + */ + *p++ = ENF_PUSHWORD + PKSWOFF_ETYPE; /* Check for ARP packet */ + *p++ = ENF_PUSHLIT | ENF_COR; + *p++ = htons(ETHERTYPE_ARP); /* Win if 0x0806 */ + + /* If didn't pass, check for our IP address */ + *p++ = ENF_PUSHWORD + PKSWOFF_IPDEST+1; + *p++ = ENF_PUSHLIT | ENF_CAND; /* Comp low wds of IP addrs */ + *p++ = htons((ucp[2]<<8) | ucp[3]); /* Make net-order low word */ + + *p++ = ENF_PUSHWORD + PKSWOFF_IPDEST; + *p++ = ENF_PUSHLIT | ENF_CAND; /* Comp high wds of IP addrs */ + *p++ = htons((ucp[0]<<8) | ucp[1]); /* Make net-order high word */ + + *p++ = ENF_PUSHWORD + PKSWOFF_ETYPE; /* Verify IP packet */ + *p++ = ENF_PUSHLIT | ENF_EQ; /* Comp, leave result on stk */ + *p++ = htons(ETHERTYPE_IP); + } else { + /* If not doing IP, fail at this point because the packet + doesn't match any of the desired types. + */ + *p++ = ENF_PUSHZERO; /* Fail - must leave 0 result on stk */ + } + + pfp->PF_FLEN = p - pfp->PF_FILT; /* Set # of items on list */ + pfp->PF_PRIO = 128; /* Pick middle of 0-255 range */ + /* "Ignored", but NIT RARPD recommends > 2 */ + if (DBGFLG) /* If debugging, print out resulting filter */ + pfshow(pfp); + return pfp; +} + + +/* Debug auxiliary to print out packetfilter we composed. +*/ +static void +pfshow(struct OSN_PFSTRUCT *pf) +{ + int i; + + fprintf(stderr,"[%s: kernel packetfilter pri %d, len %d:", progname, + pf->PF_PRIO, pf->PF_FLEN); + for (i = 0; i < pf->PF_FLEN; ++i) + fprintf(stderr, " %04X", pf->PF_FILT[i]); + fprintf(stderr, "]\r\n"); +} + +#endif /* KLH10_NET_PFLT || KLH10_NET_NIT || KLH10_NET_DLPI */ + + +#if KLH10_NET_DLPI +/* GROSS HACK for Solaris DLPI!! +** Even if the interface is dedicated, there is no way to grab packets +** for all SAPs (ethernet types) without making the interface promiscuous! +** So we have to have a filter on hand that can be used to get only +** the packets that are broadcast/multicast or specifically addressed to +** our ethernet address. Ugh! +*/ + +struct OSN_PFSTRUCT * +pfeabuild(void *arg, unsigned char *ea) +{ + register struct dpni20_s *dpni = (struct dpni20_s *)arg; + register unsigned short *p; + register struct OSN_PFSTRUCT *pfp = &pfilter; + + p = pfp->PF_FILT; /* Get addr of filter (length ENMAXFILTERS) */ + + /* First check for broadcast/multicast bit in dest address */ + *p++ = ENF_PUSHWORD + PKSWOFF_EDEST; +#ifdef ENF_PUSHONE /* New feature */ + if (htons(0x0100) == 01) { + *p++ = ENF_PUSHONE | ENF_AND; /* Do AND of multicast bit */ + *p++ = ENF_PUSHONE | ENF_COR; /* Succeed immediately if AND won */ + } else +#endif + { + *p++ = ENF_PUSHLIT | ENF_AND; /* Do AND of multicast bit */ + *p++ = htons(0x0100); + *p++ = ENF_PUSHLIT | ENF_COR; /* Succeed immediately if AND won */ + *p++ = htons(0x0100); + } + + /* Now check for our ethernet address, low bytes first since those + ** tend to vary the most. + ** Note stack is empty at this point. + */ + *p++ = ENF_PUSHWORD + PKSWOFF_EDEST+2; /* Comp low wds of E/N addrs */ + *p++ = ENF_PUSHLIT | ENF_CAND; /* Fail if not same */ + *p++ = htons((ea[4]<<8) | ea[5]); /* (net-order low word) */ + + *p++ = ENF_PUSHWORD + PKSWOFF_EDEST+1; /* Comp mid wds of E/N addrs */ + *p++ = ENF_PUSHLIT | ENF_CAND; /* Fail if not same */ + *p++ = htons((ea[2]<<8) | ea[3]); /* (net-order mid word) */ + + *p++ = ENF_PUSHWORD + PKSWOFF_EDEST; /* Comp hi wds of E/N addrs */ + *p++ = ENF_PUSHLIT | ENF_CAND; /* Fail if not same */ + *p++ = htons((ea[0]<<8) | ea[1]); /* (net-order hi word) */ + + /* Stack is again empty at this point, which means "accept packet". */ + + + pfp->PF_FLEN = p - pfp->PF_FILT; /* Set # of items on list */ + pfp->PF_PRIO = 128; /* Pick middle of 0-255 range */ + /* "Ignored", but NIT RARPD recommends > 2 */ + + if (DBGFLG) /* If debugging, print out resulting filter */ + pfshow(pfp); + return pfp; +} +#endif /* KLH10_NET_DLPI */ + +/* BPF packetfilter initialization */ + +#if KLH10_NET_BPF + +/* +** BPF filter program stuff. +** Note that you can also obtain a specific filter program for a given +** expression by using tcpdump(1) with the -d option, for example: +** tcpdump -d -s 1514 ip dst host 1.2.3.4 +** produces: + { 0x20, 0, 0, 0x0000001e }, // (000) ld [30] + { 0x15, 0, 3, 0x01020304 }, // (001) jeq #0x1020304 jt 2 jf 5 + { 0x28, 0, 0, 0x0000000c }, // (002) ldh [12] + { 0x15, 0, 1, 0x00000800 }, // (003) jeq #0x800 jt 4 jf 5 + { 0x06, 0, 0, 0x000005ea }, // (004) ret #1514 + { 0x06, 0, 0, 0x00000000 }, // (005) ret #0 +*/ + + +#define OSN_PFSTRUCT bpf_program +#define PF_FLEN bf_len +#define PF_FILT bf_insns + +#define BPF_PFMAX 50 /* Max instructions in BPF filter */ +struct bpf_insn bpf_pftab[BPF_PFMAX]; +struct bpf_program bpf_pfilter = { 0, + bpf_pftab }; + +struct bpf_insn bpf_stmt(unsigned short code, bpf_u_int32 k) +{ + struct bpf_insn ret; + ret.code = code; + ret.jt = 0; + ret.jf = 0; + ret.k = k; + return ret; +} + +struct bpf_insn bpf_jump(unsigned short code, bpf_u_int32 k, + unsigned char jt, unsigned char jf) +{ + struct bpf_insn ret; + ret.code = code; + ret.jt = jt; + ret.jf = jf; + ret.k = k; + return ret; +} + +/* BPF simple Loads */ +#define BPFI_LD(a) bpf_stmt(BPF_LD+BPF_W+BPF_ABS,(a)) /* Load word P[a:4] */ +#define BPFI_LDH(a) bpf_stmt(BPF_LD+BPF_H+BPF_ABS,(a)) /* Load short P[a:2] */ +#define BPFI_LDB(a) bpf_stmt(BPF_LD+BPF_B+BPF_ABS,(a)) /* Load byte P[a:1] */ + +/* BPF Jumps and skips */ +#define BPFI_J(op,k,t,f) bpf_jump(BPF_JMP+(op)+BPF_K,(k),(t),(f)) +#define BPFI_JEQ(k,n) BPFI_J(BPF_JEQ,(k),(n),0) /* Jump if A == K */ +#define BPFI_JNE(k,n) BPFI_J(BPF_JEQ,(k),0,(n)) /* Jump if A != K */ +#define BPFI_JGT(k,n) BPFI_J(BPF_JGT,(k),(n),0) /* Jump if A > K */ +#define BPFI_JLE(k,n) BPFI_J(BPF_JGT,(k),0,(n)) /* Jump if A <= K */ +#define BPFI_JGE(k,n) BPFI_J(BPF_JGE,(k),(n),0) /* Jump if A >= K */ +#define BPFI_JLT(k,n) BPFI_J(BPF_JGE,(k),0,(n)) /* Jump if A < K */ +#define BPFI_JDO(k,n) BPFI_J(BPF_JSET,(k),(n),0) /* Jump if A & K */ +#define BPFI_JDZ(k,n) BPFI_J(BPF_JSET,(k),0,(n)) /* Jump if !(A & K) */ + +#define BPFI_CAME(k) BPFI_JEQ((k),1) /* Skip if A == K */ +#define BPFI_CAMN(k) BPFI_JNE((k),1) /* Skip if A != K */ +#define BPFI_CAMG(k) BPFI_JGT((k),1) /* Skip if A > K */ +#define BPFI_CAMLE(k) BPFI_JLE((k),1) /* Skip if A <= K */ +#define BPFI_CAMGE(k) BPFI_JGE((k),1) /* Skip if A >= K */ +#define BPFI_CAML(k) BPFI_JLT((k),1) /* Skip if A < K */ +#define BPFI_TDNN(k) BPFI_JDO((k),1) /* Skip if A & K */ +#define BPFI_TDNE(k) BPFI_JDZ((k),1) /* Skip if !(A & K) */ + +/* BPF Returns */ +#define BPFI_RET(n) bpf_stmt(BPF_RET+BPF_K, (n)) /* Return N bytes */ +#define BPFI_RETFAIL() BPFI_RET(0) /* Failure return */ +#define BPFI_RETWIN() BPFI_RET((u_int)-1) /* Success return */ + +static void pfshow(struct OSN_PFSTRUCT *); + +struct OSN_PFSTRUCT * +pfbuild(void *arg, struct in_addr *ipa) +{ + register struct dpni20_s *dpni = (struct dpni20_s *)arg; + register struct OSN_PFSTRUCT *pfp = &bpf_pfilter; + register struct bpf_insn *p; + + p = pfp->PF_FILT; /* Point to 1st instruction in BPF program */ + + /* First check for broadcast/multicast bit in dest address */ + *p++ = BPFI_LDB(PKBOFF_EDEST); /* Get 1st byte of dest ether addr */ + *p++ = BPFI_TDNE(01); /* Skip if bit is zero */ + *p++ = BPFI_RETWIN(); /* Bit set, succeed immediately! */ + + /* Possibly insert check for DECNET protocol types. + ** Doing this check is inefficient if most of the traffic is IP. + ** Hopefully if the user asked for it, it's used a lot. + ** The following are the known types: + ** 6001 DNA/MOP + ** 6002 RmtCon + ** 6003 DECnet + ** 6004 LAT + ** 6016 ANF-10 (T10 only; not DECNET) + ** 9000 Loopback (?) + ** + ** For the time being, filtering is done by testing for + ** (type & ~0x001F) == 0x6000 + ** which accepts all types in the range 6000-601F inclusive. + ** 9000 is ignored. + */ + if (dpni->dpni_decnet) { +#if 0 + /* Blunt instrument approach */ + *p++ = BPFI_LDH(PKBOFF_ETYPE); /* Get ethernet type */ + + *p++ = BPFI_CAMN(ETHERTYPE_DECnet); /* Win if 0x6003 */ + *p++ = BPFI_RETWIN(); /* Win now! */ + *p++ = BPFI_CAMN(ETHERTYPE_LAT); /* Win if 0x6004 */ + *p++ = BPFI_RETWIN(); /* Win now! */ + *p++ = BPFI_CAMN(0x6016); /* Check for 0x6016 */ + *p++ = BPFI_RETWIN(); /* Win now! */ + *p++ = BPFI_CAMN(0x6001); /* Check for 0x6001 */ + *p++ = BPFI_RETWIN(); /* Win now! */ + *p++ = BPFI_CAMN(0x6002); /* Check for 0x6002 */ + *p++ = BPFI_RETWIN(); /* Win now! */ +#else + /* Slightly faster, although sloppier */ + *p++ = BPFI_LDH(PKBOFF_ETYPE); /* Get ethernet type */ + *p++ = bpf_stmt(BPF_ALU+BPF_AND+BPF_K, 0xFFE0); /* Mask out ~0x001F */ + *p++ = BPFI_CAMN(0x6000); /* Succeed if result 6000 */ + *p++ = BPFI_RETWIN(); /* Win now! */ +#endif + } + + + /* Test for an IEEE 802.3 packet with a specific dst/src LSAP. + Packet is 802.3 if type field is actually packet length -- in which + case it will be 1 <= len <= 1500 (note 1514 is max, of which header + uses 6+6+2=14). + There's seemingly no way to tell what order the ENF_LT, etc operands + are used in, so until that's established, use a simple masking + method. 1500 = 0x5dc, so use mask of 0xF800. + + Dst/src LSAPs are in next two bytes (1st shortwd after len). + */ + if (dpni->dpni_attrs & DPNI20F_LSAP) { + unsigned short lsaps = dpni->dpni_lsap; + + if (lsaps <= 0xFF) { /* If only one byte set, */ + lsaps |= (lsaps << 8); /* double it up for both dest & src */ + } + + *p++ = BPFI_LDH(PKBOFF_ETYPE); /* Get ethernet type */ + *p++ = BPFI_JGT(1500, 3); /* If > 1500, skip next 3 insns */ + *p++ = BPFI_LDH(PKBOFF_SAPS); /* Get DSAP/SSAP shortwd */ + *p++ = BPFI_CAMN(lsaps); /* Matches? */ + *p++ = BPFI_RETWIN(); /* Yes, win now! */ + + } + + /* See if we're interested in IP (and thus ARP) packets. + This is assumed to be the LAST part of the filter, thus + it must either leave the correct result on the stack, or + ensure it is empty (if accepting the packet). + */ + if (memcmp(dpni->dpni_ip, "\0\0\0\0", 4) != 0) { + + /* Want to pass ARP replies as well, so 10 can see responses to any + ** ARPs it sends out. + ** NOTE!!! ARP *requests* are not passed! The assumption is that + ** osn_arp_stuff() will have ensured that the host platform + ** proxy-answers requests for our IP address. + */ + *p++ = BPFI_LDH(PKBOFF_ETYPE); /* Load ethernet type field */ + *p++ = BPFI_CAMN(ETHERTYPE_ARP); /* Skip unless ARP packet */ + *p++ = BPFI_RETWIN(); /* If ARP, win now! */ + + /* If didn't pass, check for our IP address */ + *p++ = BPFI_LD(PKBOFF_IPDEST); /* Get IP dest address */ + *p++ = BPFI_CAME(ntohl(ipa->s_addr)); /* Skip if matches */ + *p++ = BPFI_RETFAIL(); /* Nope, fail */ + + /* Passed IP check, one last thing... */ + *p++ = BPFI_LDH(PKBOFF_ETYPE); /* Load ethernet type field */ + *p++ = BPFI_CAMN(ETHERTYPE_IP); /* Skip unless IP packet */ + *p++ = BPFI_RETWIN(); /* If IP, win now! */ + *p++ = BPFI_RETFAIL(); /* Nope, fail */ + + } else { + /* If not doing IP, fail at this point because the packet + doesn't match any of the desired types. + */ + *p++ = BPFI_RETFAIL(); /* Fail */ + } + + pfp->PF_FLEN = p - pfp->PF_FILT; /* Set # of items on list */ + + if (DBGFLG) /* If debugging, print out resulting filter */ + pfshow(pfp); + return pfp; +} + + +/* Debug auxiliary to print out packetfilter we composed. +*/ +static void +pfshow(struct OSN_PFSTRUCT *pf) +{ + int i; + + fprintf(stderr, "[%s: kernel packetfilter pri <>, len %d:\r\n", + progname, + /* pf->PF_PRIO, */ pf->PF_FLEN); + for (i = 0; i < pf->PF_FLEN; ++i) + fprintf(stderr, "%04X %2d %2d %0X\r\n", + pf->PF_FILT[i].code, + pf->PF_FILT[i].jt, + pf->PF_FILT[i].jf, + pf->PF_FILT[i].k); + fprintf(stderr, "]\r\n"); +} + +#endif /* KLH10_NET_BPF */ + +/* LNX packetfilter initialization */ + +#if KLH10_NET_LNX + +/* + The Linux PF_PACKET interface is described to some extent + by the packet(7) man page. + + Linux provides no kernel packet filtering mechanism other than + possibly a check on the ethernet protocol type, but this is useless + for us since we'll always want to check for more than just one type; + e.g. IP and ARP, plus possibly 802.3 or DECNET packets. + + From the man page for packet(7): + By default all packets of the specified protocol type are + passed to a packet socket. To only get packets from a spe- + cific interface use bind(2) specifying an address in a + struct sockaddr_ll to bind the packet socket to an inter- + face. Only the sll_protocol and the sll_ifindex address + fields are used for purposes of binding. + */ + +/* Because LNX has no kernel packet filtering, must do it + manually. Ugh! + + Call this when using a non-dedicated interface. + Returns TRUE if packet OK, FALSE if it should be dropped. + Note that the code parallels that for pfbuild(). +*/ +int lnx_filter(register struct dpni20_s *dpni, + unsigned char *bp, + int cnt) +{ + /* Code assumes buffer is at least shortword-aligned. */ + register unsigned short *sp = (unsigned short *)bp; + register unsigned short etyp; + + /* First check for broadcast/multicast bit in dest address */ + if (bp[PKBOFF_EDEST] & 01) + return TRUE; /* Bit set, succeed immediately! */ + + /* Now get ethernet protocol type for further checking. + Could also test packet length, but for now assume higher level + will take care of those checks. + */ + etyp = ntohs(sp[PKSWOFF_ETYPE]); + switch (etyp) { + + case ETHERTYPE_ARP: + /* Always interested in ARP, unless no IP address */ + return (memcmp(dpni->dpni_ip, "\0\0\0\0", 4) != 0); + + case ETHERTYPE_IP: + /* For IP packet, return TRUE if IP destination matches ours */ + return (memcmp(dpni->dpni_ip, bp + PKBOFF_IPDEST, 4) == 0); + + /* Check for DECNET protocol types if requested. + ** The following are the known types: + ** 6001 DNA/MOP + ** 6002 RmtCon + ** 6003 DECnet + ** 6004 LAT + ** 6016 ANF-10 (T10 only; not DECNET) + ** 9000 Loopback (?) + */ + case 0x6001: /* DNA/MOP */ + case 0x6002: /* RmtCon */ + case 0x6003: /* DECnet */ + case 0x6004: /* LAT */ + case 0x6016: /* ANF-10 (T10 only; not DECNET) */ + case 0x9000: /* Loopback (?) */ + return (dpni->dpni_decnet); /* TRUE if wanted Decnet stuff */ + + default: + /* Test for an IEEE 802.3 packet with a specific dst/src LSAP. + Packet is 802.3 if type field is actually packet length -- in which + case it will be 1 <= len <= 1500 (note 1514 is max, of which header + uses 6+6+2=14). + + Dst/src LSAPs are in the 1st shortwd after packet length. + */ + if (etyp <= 1500 + && (dpni->dpni_attrs & DPNI20F_LSAP) + && (dpni->dpni_lsap == sp[PKSWOFF_SAPS])) + return TRUE; + break; + } + return FALSE; +} + +#endif /* KLH10_NET_LNX */ + + +/* ETH_SETADR - Attempt to set physical ethernet address to dpni_rqeth. +** If successful, reflect this by changing dpni_eth. +*/ +void eth_adrset(register struct dpni20_s *dpni) +{ + unsigned char rdea[ETHER_ADRSIZ]; + char old[OSN_EASTRSIZ]; + char new[OSN_EASTRSIZ]; + + /* Set up for simpler output */ + eth_adrsprint(old, dpni->dpni_eth); + eth_adrsprint(new, dpni->dpni_rqeth); + + /* Before hitting the barf below, do one last check to make sure + ** we're not setting it to the current address. + */ + if (memcmp(dpni->dpni_eth, dpni->dpni_rqeth, ETHER_ADRSIZ) == 0) + return; /* Succeed silently */ + + + /* Check to make sure it's OK to set our address. + ** Only allow it if interface is dedicated; otherwise, barf so user + ** knows it has to be set manually. + */ + if (!dpni->dpni_dedic) { + /* Actually, allow it if DECNET, unless interface is *already* + ** a DECNET address. DECNET addrs are always AA:00:04:... + */ + if (dpni->dpni_decnet) { + static unsigned char dnpref[3] = { 0xAA, 0x00, 0x04 }; + if (memcmp(dpni->dpni_eth, dnpref, 3) == 0) { + dbprintln("\"%s\" E/N addr change ignored, Old=%s New=%s - already a DECNET addr!", + dpni->dpni_ifnam, old, new); + return; + } + } else { + dbprintln("\"%s\" E/N addr change ignored, Old=%s New=%s - interface not dedicated", + dpni->dpni_ifnam, old, new); + return; + } + } + + if (!osn_ifeaset(-1, dpni->dpni_ifnam, dpni->dpni_rqeth)) { + error("\"%s\" E/N addr change failed, Old=%s New=%s", + dpni->dpni_ifnam, old, new); + return; + } + + /* Always print out, to inform user */ + if (1) { + dbprintln("\"%s\" E/N addr changed: Old=%s New=%s", + dpni->dpni_ifnam, old, new); + } + + /* Apparently won! Try reading it back just to be paranoid, + * using packetfilter FD. + */ + if (!osn_pfeaget(pffd, dpni->dpni_ifnam, rdea)) { + error("Can't read \"%s\" e/n addr!", dpni->dpni_ifnam); + /* Proceed as if set won, sigh */ + } else { + + /* See if same as requested! */ + if (memcmp(rdea, dpni->dpni_rqeth, ETHER_ADRSIZ) != 0) { + eth_adrsprint(old, rdea); + dbprintln("New \"%s\" e/n addr mismatch! Set=%s Read=%s", + dpni->dpni_ifnam, new, old); + } + } + + /* Assume succeeded since call succeeded, and clobber our address! */ + memcpy(dpni->dpni_eth, dpni->dpni_rqeth, ETHER_ADRSIZ); +} + + +/* ETH_MCATSET - Set multicast addresses. +** Problem here is that there is no apparent way of READING the hardware's +** current multicast addresses! +** So, unless the OS or hardware is clever about recognizing duplicates, +** successive runs of this routine could fill the table up. Sigh. +** +** Another hassle is that we need to keep track of the MCAT so that it's +** possible to tell when addresses are removed from the table by new +** MCAT loads. +*/ + +void eth_mcatset(register struct dpni20_s *dpni) +{ + ossock_t s; + int i, n, j; + char ethstr[OSN_EASTRSIZ]; + + /* Check to make sure it's OK to set the multicast table. + ** Only allow it if interface is dedicated; otherwise, barf so user + ** knows it has to be set manually. + */ + if (!dpni->dpni_dedic && !dpni->dpni_decnet) { + dbprintln("\"%s\" multicast table ignored - interface not dedicated", + dpni->dpni_ifnam); + return; + } + + /* Dunno if packetfilter FD would pass these through, so get another + ** socket FD for this purpose. + */ + if (!osn_ifsock(dpni->dpni_ifnam, &s)) { + syserr(errno, "multicast table set failed - osn_ifsock"); + return; + } + + /* First flush any old entries that aren't in new table. */ + if ((n = dpni->dpni_nmcats) > DPNI_MCAT_SIZ) + n = DPNI_MCAT_SIZ; + for (i = 0; i < nmcats; ++i) { + for (j = 0; j < n; ++j) { + if (memcmp(ethmcat[i], dpni->dpni_mcat[j], 6) == 0) + break; + } + if (j < n) + continue; /* Match found, continue outer loop */ + + /* No match found for this old entry, so flush it from OS */ + if (1) { /* For now, always print out to warn user */ + dbprintln("Deleting \"%s\" multicast entry: %s", + dpni->dpni_ifnam, + eth_adrsprint(ethstr, ethmcat[i])); + } + if (!osn_ifmcset(s, dpni->dpni_ifnam, TRUE /*DEL*/, ethmcat[i])) { + error("\"%s\" Multicast delete failed", dpni->dpni_ifnam); + /* Keep going */ + } + } + + /* Now grovel in other direction, to find all addrs not already + ** in old table. + */ + for (j = 0; j < n; ++j) { + for (i = 0; i < nmcats; ++i) { + if (memcmp(ethmcat[i], dpni->dpni_mcat[j], 6) == 0) + break; + } + if (j < n) + continue; /* Match found, continue outer loop */ + + /* No match found for this new entry, so add it to OS */ + if (1) { /* For now, always print out to warn user */ + dbprintln("Adding \"%s\" multicast entry: %s", + dpni->dpni_ifnam, + eth_adrsprint(ethstr, dpni->dpni_mcat[i])); + } + if (!osn_ifmcset(s, dpni->dpni_ifnam, FALSE /*ADD*/, + dpni->dpni_mcat[i])) { + error("\"%s\" Multicast add failed", dpni->dpni_ifnam); + /* Keep going */ + } + } + + /* Done, close socket and copy new table */ + osn_ifclose(s); + + nmcats = n; + memcpy(ethmcat[0], dpni->dpni_mcat[0], (n * 6)); +} + +/* ARP Hackery */ + +/* ARP_REQCHECK +** Check to see if outbound ARP packet is a query to our own host +** platform. If so, drops it and generates a reply ourselves. +** +** This is specially rigged so it only sends one particular kind of +** reply -- the KLH10 is doing a proxy reply for the host platform, +** for the case where the KLH10 just sent out an ARP request for +** its platform! +** +** This is needed because OSF/1 doesn't process ARP packets sent by +** packetfilters, so it neither responds to requests nor sees replies. +** The intent is that this packet will be looped back into the read +** side of the DPNI20 and thus answer the KLH10's ARP request. +** +** NOTE! Although ordinarily the ARP reply should be addressed to +** the correct ethernet target, here we use the broadcast address +** because that's the only way to get it past the receive side's +** packetfilter (short of slowing things down with another header test +** that checks for and passes ethertype ARP). There should only be +** one such packet for every time a monitor is started on the KLH10 +** so this isn't too bad in the way of net citizenship. +*/ +#define arp_reqcheck(p, cnt) ( \ + (((struct ether_header *)p)->ether_type == htons(ETHERTYPE_ARP)) \ + && (cnt >= ARP_PKTSIZ) \ + && (((struct ether_arp *)(p+ETHER_HDRSIZ))->arp_op == htons(ARPOP_REQUEST))) + +#define ARP_PKTSIZ (sizeof(struct ether_header) + sizeof(struct ether_arp)) + +int arp_myreply(register unsigned char *buf, register int cnt) +{ + register struct ifent *ife; + register unsigned char *ucp; + struct in_addr ia; + struct ether_arp arp; + unsigned char pktbuf[ARP_PKTSIZ]; + + /* Have an ARP request. Carry out final check to be sure + ** the request is for an IP address belonging to our native + ** host, which we must thus act as a proxy for. + */ + memcpy((void *)&ia, /* Copy IP addr to ensure aligned */ + (void *)((struct ether_arp *)(buf+ETHER_HDRSIZ))->arp_tpa, + IP_ADRSIZ); + if ((ife = osn_iftab_arp(ia)) == NULL) + return FALSE; /* Request for host we dunno about */ + + /* Found it! ife now points to matching entry */ + if (!ife->ife_gotea) { + if (!osn_ifeaget(-1, ife->ife_name, ife->ife_ea, + (unsigned char *)NULL)) { + error("ARP MyReply failed, no E/N addr for %s", ife->ife_name); + return FALSE; + } + ife->ife_gotea = TRUE; /* Remember found the EN addr */ + } + + /* Now build ARP reply */ + + /* First do ethernet header. Can't use ether_header cuz + ** different systems have defined it in incompatible ways so it's + ** impossible to win with C code. This is progress? + ** Same problem afflicts arp_sha and arp_tha, which are sometimes + ** "uchar[]" and sometimes "struct ether_addr". + */ + ucp = pktbuf; +#if 0 /* No! See note above... must use bcast addr */ + ea_set(ucp, (char *)&ihost_ea); /* First do dest addr */ +#else + memset(ucp, 0xFF, ETHER_ADRSIZ); /* Set dest to bcast */ +#endif + ucp += ETHER_ADRSIZ; + ea_set(ucp, (char *)&ihost_ea); /* Set source addr to ours! */ + ucp += ETHER_ADRSIZ; + *ucp++ = (ETHERTYPE_ARP>>8)&0377; /* Set high byte of type */ + *ucp++ = (ETHERTYPE_ARP )&0377; /* Set low byte of type */ + + /* Now put together the ARP packet */ + arp.arp_hrd = htons(ARPHRD_ETHER); /* Set hdw addr format */ + arp.arp_pro = htons(ETHERTYPE_IP); /* Set ptcl addr fmt */ + arp.arp_hln = sizeof(arp.arp_sha); /* Hdw address len */ + arp.arp_pln = sizeof(arp.arp_spa); /* Ptcl address len */ + arp.arp_op = htons(ARPOP_REPLY); /* Type REPLY */ + + /* Sender hdw addr and IP addr for host platform */ +#if 0 /* CENV_SYS_SOLARIS */ + memcpy((char *)&arp.arp_sha, ife->ife_ea, ETHER_ADRSIZ); +#else + memcpy((char *)arp.arp_sha, ife->ife_ea, ETHER_ADRSIZ); +#endif + memcpy((char *)arp.arp_spa, ife->ife_ipchr, IP_ADRSIZ); + + /* Target hdw addr and IP addr for emulated 20 */ +#if 0 /* CENV_SYS_SOLARIS */ + memcpy((char *)&arp.arp_tha, (char *)&ihost_ea, ETHER_ADRSIZ); +#else + memcpy((char *)arp.arp_tha, (char *)&ihost_ea, ETHER_ADRSIZ); +#endif + memcpy((char *)arp.arp_tpa, (char *)&ehost_ip, IP_ADRSIZ); + + /* Now build raw packet. Do it this way to avoid potential + ** problems with padding introduced by structure alignment. + */ + memcpy(ucp, (char *)&arp, sizeof(struct ether_arp)); + + /* Now send it! Ignore any errors. */ + if (swstatus) { + char ipstr[OSN_IPSTRSIZ]; + dbprintln("ARP MyReply %s", ip_adrsprint(ipstr, ife->ife_ipchr)); + } + +#if KLH10_NET_DLPI + { + struct strbuf data; + data.buf = (char *)pktbuf; + data.len = sizeof(pktbuf); + (void) putmsg(pffd, NULL, &data, 0); + } +#else + (void)write(pffd, pktbuf, sizeof(pktbuf)); +#endif + return TRUE; +} + +/* ETHTOTEN - Main loop for thread pumping packets from Ethernet to 10. +** Reads packets from packetfilter and relays them to 10 using DPC +** mechanism. +*/ +/* Screwy BPF algorithm requires more bookkeeping because there's + no way to ensure only one packet is read at a time; the call + may return a buffer of several packets, each of which must + be returned to the 10 separately. + Also, we need to do buffer copying since the 10 side doesn't + understand the BPF header. Later an offset could be provided? +*/ + +#define MAXETHERLEN 1600 /* Actually 1519 but be generous */ + +void ethtoten(register struct dpni20_s *dpni) +{ + register struct dpx_s *dpx; + register int cnt; + register unsigned char *buff; + size_t max; + int cmin = sizeof(struct ether_header); + int stoploop = 50; + + dpx = dp_dpxfr(&dp); /* Get ptr to from-DP comm rgn */ + buff = dp_xsbuff(dpx, &max); /* Set up buffer ptr & max count */ + + /* Tell KLH10 we're initialized and ready by sending initial packet */ + dp_xswait(dpx); /* Wait until buff free, in case */ + dp_xsend(dpx, DPNI_INIT, 0); /* Send INIT */ + + if (DBGFLG) + dbprintln("sent INIT"); + + /* Standard algorithm, one packet per read call */ + for (;;) { +#if KLH10_NET_BPF + unsigned char *bp, *ep, *pp; + int i; + size_t caplen, hdrlen, datalen; + unsigned char tbuff[OSN_BPF_MTU]; + size_t tmax = sizeof(tbuff); + cmin = (int)(sizeof(struct ether_header)+sizeof(struct bpf_hdr)); +#endif /* KLH10_NET_BPF */ + + + /* Make sure that buffer is free before clobbering it */ + dp_xswait(dpx); /* Wait until buff free */ + + if (DBGFLG) + dbprintln("InWait"); + + /* OK, now do a blocking read on packetfilter input! */ +#if KLH10_NET_DLPI + { + struct strbuf data; + int flagsp = 0; + + data.buf = (char *)buff; + data.maxlen = max; + data.len = 0; + if ((cnt = getmsg(pffd, (struct strbuf *)NULL, &data, &flagsp)) == 0) + cnt = data.len; + /* Else cnt must be -1 as call failed */ + } +#elif KLH10_NET_NIT || KLH10_NET_PFLT || KLH10_NET_LNX + cnt = read(pffd, buff, max); +#elif KLH10_NET_BPF + cnt = read(pffd, tbuff, tmax); +#endif + if (cnt <= cmin) { /* Must get enough for ether header */ + + /* If call timed out, should return 0 */ + if (cnt == 0 && dpni->dpni_rdtmo) + continue; /* Just try again */ + if (DBGFLG) + dbprintln("ERead=%d, Err=%d", cnt, errno); + + if (cnt < 0 && (errno == EINTR)) /* Ignore spurious signals */ + continue; + + /* Error of some kind */ + if (cnt < 0) { + syserr(errno, "Eread = %d, errno %d", cnt, errno); + if (--stoploop <= 0) + efatal(1, "Too many retries, aborting"); + } else + dbprintln("Eread = %d, %s", cnt, + (cnt > 0) ? "no ether data" : "no packet"); + + continue; /* For now... */ + } + + if (DBGFLG) { + if (DBGFLG & 0x4) { + fprintf(stderr, "\r\n[%s: Read=%d\r\n", progname, cnt); +#if KLH10_NET_BPF + dumppkt(tbuff, cnt); +#else + dumppkt(buff, cnt); +#endif + fprintf(stderr, "]"); + } + else + dbprint("Read=%d", cnt); + } +#if KLH10_NET_LNX + /* Linux has no packet filtering, thus must apply manual check to + each and every packet read, unless dedicated. Ugh! + */ + if (!dpni->dpni_dedic) { + /* Sharing interface. Check for IP, DECNET, 802.3 */ + if (!lnx_filter(dpni, buff, cnt)) + continue; /* Drop packet, continue reading */ + } +#endif /* KLH10_NET_LNX */ +#if KLH10_NET_NIT || KLH10_NET_DLPI || KLH10_NET_PFLT || KLH10_NET_LNX +#if 0 + if (DBGFLG) + if (((struct ether_header *)buff)->ether_type == htons(ETHERTYPE_ARP)) + dbprintln("Got ARP"); +#endif + + /* Normal packet, pass to 10 via DPC */ + dp_xsend(dpx, DPNI_RPKT, cnt); + if (DBGFLG) + dbprint("sent RPKT"); + +#endif /* KLH10_NET_NIT || KLH10_NET_DLPI || KLH10_NET_PFLT || KLH10_NET_LNX */ + +#if KLH10_NET_BPF + /* Screwy BPF algorithm requires more overhead because there's + no way to ensure only one packet is read at a time; the call + may return a buffer of several packets, each of which must + be returned to the 10 separately. + Also, we need to do buffer copying since the 10 side doesn't + understand the BPF header. Later an offset could be provided? + */ + + /* Grovel through buffer, sending each packet up to 10. + */ + bp = tbuff; + ep = bp + cnt; +# define bhp(p) ((struct bpf_hdr *)(p)) + caplen = bhp(bp)->bh_caplen; /* Pre-fetch first BPF header */ + datalen = bhp(bp)->bh_datalen; + hdrlen = bhp(bp)->bh_hdrlen; + for (i = 0; bp < ep; ++i) { + + if (caplen != datalen) { + dbprint("BPF#%d trunc %ld => %ld", + i, (long)datalen, (long)caplen); + /* Continue, not much else we can do */ + } + + cnt = caplen; + pp = bp + hdrlen; /* Pointer to actual packet data */ + + /* Point to next header now, before current one is trashed */ + bp += BPF_WORDALIGN(caplen + hdrlen); + if (bp < ep) { + caplen = bhp(bp)->bh_caplen; + datalen = bhp(bp)->bh_datalen; + hdrlen = bhp(bp)->bh_hdrlen; + } +# undef bhp + if (DBGFLG) + dbprint("BPF pkt %d = %d", i, cnt); + + /* Copy packet data into buffer for 10, and send it */ + memcpy((char *)buff, (char *)pp, (size_t)cnt); + dp_xsend(dpx, DPNI_RPKT, cnt); + if (DBGFLG) + dbprint("sent RPKT"); + + /* Wait until send ACKed, assume buff still OK */ + dp_xswait(dpx); + } +#endif /* KLH10_NET_BPF */ + + } /* Infinite loop reading packetfilter input */ +} + +/* TENTOETH - Main loop for thread pumping packets from 10 to Ethernet. +** Reads DPC message from 10 and interprets it; if a regular +** data message, sends to ethernet. +*/ + +void tentoeth(register struct dpni20_s *dpni) +{ + register struct dpx_s *dpx; + register int cnt; + register unsigned char *buff; + size_t max; + register int rcnt; + register int doarpchk; + int stoploop = 50; + + /* Must check for outbound ARP requests if asked to and have + ** at least one entry in our table of host's IP interfaces. + */ + doarpchk = (dpni->dpni_doarp & DPNI_ARPF_OCHK) && (osn_nifents() > 0); + + dpx = dp_dpxto(&dp); /* Get ptr to "To-DP" xfer stuff */ + buff = dp_xrbuff(dpx, &max); + + if (DBGFLG) + dbprintln("Starting loop"); + + for (;;) { + + if (DBGFLG) + dbprintln("CmdWait"); + + /* Wait until 10 has a command for us */ + dp_xrwait(dpx); /* Wait until something there */ + + /* Process command from 10! */ + switch (dp_xrcmd(dpx)) { + + case DPNI_SPKT: /* Send regular packet */ + rcnt = dp_xrcnt(dpx); + if (DBGFLG) { + if (DBGFLG & 0x2) + { + fprintf(stderr, "\r\n[%s: Sending %d\r\n", progname, rcnt); + dumppkt(buff, rcnt); + fprintf(stderr, "]"); + } + else + dbprint("SPKT %d", progname, rcnt); + } + if (doarpchk /* If must check ARPs */ + && arp_reqcheck(buff, rcnt) /* and this is an ARP req */ + && arp_myreply(buff, rcnt)) { /* and it fits, & is hacked */ + break; /* then drop this req pkt */ + } +#if KLH10_NET_DLPI + { + struct strbuf data; + data.buf = (char *)buff; + data.len = rcnt; + if ((cnt = putmsg(pffd, NULL, &data, 0)) == 0) + cnt = rcnt; /* Assume successful */ + } +#else + cnt = write(pffd, buff, rcnt); +#endif + if (cnt != rcnt) { + if ((cnt < 0) && (errno == EINTR)) { + continue; /* Start over, may have new cmd */ + } + syserr(errno, "PF write %d != %d, errno %d", cnt, rcnt, errno); + if (--stoploop <= 0) + efatal(1, "Too many retries, aborting"); + continue; + } + break; + + case DPNI_SETETH: + /* Attempt to change physical ethernet addr */ + if (DBGFLG) + dbprint("SETETH"); + eth_adrset(dpni); + break; + + case DPNI_SETMCAT: + /* Attempt to change MCAT (multicast table) */ + if (DBGFLG) + dbprint("SETMCAT"); + eth_mcatset(dpni); + break; + + case DPNI_RESET: + /* Attempt to do complete reset */ + dbprint("RESET"); +#if 0 + dpni_restart(2); +#endif + break; + + default: + dbprintln("Unknown cmd %d", dp_xrcmd(dpx)); + break; + } + + /* Command done, tell 10 we're done with it */ + if (DBGFLG) + dbprint("CmdDone"); + + dp_xrdone(dpx); + } +} + +void dumppkt(unsigned char *ucp, int cnt) +{ + int i; + + while (cnt > 0) { + for (i = 8; --i >= 0 && cnt > 0;) { + if (--cnt >= 0) + fprintf(stderr, " %02x", *ucp++); + if (--cnt >= 0) + fprintf(stderr, "%02x", *ucp++); + } + fprintf(stderr, "\r\n"); + } +} + +/* Add OSDNET shared code here */ + +#include "osdnet.c" diff --git a/src/dpni20.h b/src/dpni20.h new file mode 100644 index 0000000..6da2c87 --- /dev/null +++ b/src/dpni20.h @@ -0,0 +1,88 @@ +/* DPNI20.H - Device sub-Process defs for NI20 +*/ +/* $Id: dpni20.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1994, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dpni20.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#ifndef DPNI20_INCLUDED +#define DPNI20_INCLUDED 1 + +#ifdef RCSID + RCSID(dpni20_h,"$Id: dpni20.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +#ifndef DPSUP_INCLUDED +# include "dpsup.h" +#endif + +#define DPNI_MCAT_SIZ 16 /* Size of multicast table */ +#define DPNI_PTT_SIZ 16 /* Size of protocol type table */ + +/* Version of DPNI20-specific shared memory structure */ + +#define DPNI20_VERSION DPC_VERSION(1,1,1) /* 1.1.1 */ + + +/* DPNI20-specific stuff */ + /* C = controlling parent sets, D = Device proc sets */ +struct dpni20_s { + struct dpc_s dpni_dpc; /* CD Standard DPC portion */ + int dpni_ver; /* C Version of shared struct */ + int dpni_attrs; /* C Attribute flags */ +# define DPNI20F_LSAP 0x0100 /* Set if LSAP value specified */ + int dpni_lsap; /* C Dest/Source LSAP value if needed */ + char dpni_ifnam[16]; /* CD Interface name if any */ + unsigned char dpni_eth[6]; /* CD Ethernet address of interface */ + unsigned char dpni_ip[4]; /* C 10's IP address to filter on, if shared */ + int dpni_backlog; /* C Max sys backlog of rcvd packets */ + int dpni_dedic; /* C TRUE if dedicated ifc, else shared */ + int dpni_decnet; /* C TRUE to seize DECNET packets, if shared */ + int dpni_doarp; /* C TRUE to do ARP hackery, if shared */ + int dpni_rdtmo; /* C # secs to timeout on packetfilter read */ + unsigned char dpni_rqeth[6]; /* C Requested ethernet addr */ + int dpni_nmcats; /* C # of MCAT entries */ + unsigned char dpni_mcat[DPNI_MCAT_SIZ][6]; /* C Requested MCAT */ + int dpni_nptts; /* C # of PTT entries */ + unsigned char dpni_ptt[DPNI_PTT_SIZ][6]; /* C Requested PTT */ +}; + +/* Commands to and from DP and KLH10 NI20 driver */ + + /* From 10 to DP */ +#define DPNI_RESET 0 /* Reset DP */ +#define DPNI_SPKT 1 /* Send data packet to ethernet */ +#define DPNI_SETETH 2 /* Set hardware ethernet address */ +#define DPNI_SETMCAT 3 /* Set hardware multicasts from MCAT table */ +#define DPNI_SETPTT 4 /* Set packetfilter using PTT */ + + /* From DP to 10 */ +#define DPNI_INIT 1 /* DP->10 Finished init */ +#define DPNI_RPKT 2 /* DP->10 Received data packet from net */ +#define DPNI_NEWETH 3 /* DP->10 Ethernet Address changed */ + + + /* Feature bits in dpni_doarp */ +/* 0x1 */ /* TRUE -- translate into 0xF */ +#define DPNI_ARPF_PUBL 0x2 /* Register self, publish */ +#define DPNI_ARPF_PERM 0x4 /* Register self, permanent */ +#define DPNI_ARPF_OCHK 0x8 /* Check outgoing ARPs for host platform */ + +#endif /* ifndef DPNI20_INCLUDED */ diff --git a/src/dprpxx.c b/src/dprpxx.c new file mode 100644 index 0000000..5bb10ac --- /dev/null +++ b/src/dprpxx.c @@ -0,0 +1,1011 @@ +/* DPRPXX.C - Device sub-Process for KLH10 RPxx Disk +*/ +/* $Id: dprpxx.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1994, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dprpxx.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* + + This subprocess is intended to handle either actual hardware +disk drives, or virtual disk files. + + A disk is mounted by passing it a string path argument and +setting other parameters in the shared memory area to describe format, +size, and configuration. + +*/ + +#include +#include +#include +#include /* For malloc */ +#include + +#include "klh10.h" /* For config params */ +#include "word10.h" +#include "dpsup.h" /* General DP defs */ +#include "dprpxx.h" /* RPXX specific defs */ +#include "vdisk.h" /* Virtual disk hackery */ + +#ifdef RCSID + RCSID(dprpxx_c,"$Id: dprpxx.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +#if CENV_SYS_UNIX +# include /* Include standard unix syscalls */ +# include +#endif + +struct devdk { + struct dp_s d_dp; /* Point to shared memory area */ + struct dprpxx_s *d_rp; /* Shorthand ptr to DPRPXX struct in area */ + w10_t *d_10mem; /* Ptr to 10 memory, if DMA allowed */ + unsigned long d_10siz; /* Size of 10 memory, in words */ + + int d_mntreq; /* TRUE if mount request pending */ + int d_state; +#define DPRPXX_STA_OFF 0 +#define DPRPXX_STA_ON 1 + + int d_isdisk; /* Disk type, MTYP_xxx */ + char *d_path; /* M Disk drive path spec */ +#if CENV_SYS_UNIX + int d_fd; +#endif + unsigned char *d_buff; /* Ptr to buffer loc */ + size_t d_blen; /* Actual buffer length */ + + struct vdk_unit d_vdk; /* Virtual disk info */ +} devdk; + + +void rptoten(struct devdk *); +void tentorp(struct devdk *); + +void dprpclear(struct devdk *); +#if 0 +void dprpstat(struct devdk *); +#endif + +int devmount(struct devdk *d, char *opath, int wrtf); +int devclose(struct devdk *); +int devread(struct devdk *); +int devwrite(struct devdk *); +int dmaread(struct devdk *); +int dmawrite(struct devdk *); + +void sscattn(struct devdk *d); +void chkmntreq(struct devdk *d); + +#if 0 +int os_dkopen(), os_dkread(), os_dkwrite(), os_dkclrerr(); +int os_dkfsr(), os_dkshow(); +#endif + + +/* For now, include VDISK source directly, so as to avoid compile-time +** switch conflicts (eg with KLH10). +*/ +#include "vdisk.c" + +#define DBGFLG (devdk.d_rp->dprp_dpc.dpc_debug) + + +/* Low-level support */ + + +static void efatal(int num, char *fmt, ...) +{ + fprintf(stderr, "\n%s: ", "dprpxx"); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + putc('\n', stderr); + + dp_exit(&devdk.d_dp, num); +} + +static void esfatal(int num, char *fmt, ...) +{ + fprintf(stderr, "\n%s: ", "dprpxx"); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fprintf(stderr, " - %s\n", dp_strerror(errno)); + + dp_exit(&devdk.d_dp, num); +} + +static void error(char *fmt, ...) +{ + fprintf(stderr, "\n%s: ", "dprpxx"); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } +} + +static void syserr(int num, char *fmt, ...) +{ + fprintf(stderr, "\n%s: ", "dprpxx"); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fprintf(stderr, " - %s\n", dp_strerror(num)); +} + +int +main(int argc, char **argv) +{ + register struct devdk *d = &devdk; + + /* General initialization */ + if (!dp_main(&d->d_dp, argc, argv)) { + efatal(1, "DP init failed!"); + } + d->d_rp = (struct dprpxx_s *)d->d_dp.dp_adr; /* Make refs easier */ + + /* After this point, can check DBGFLG */ + if (DBGFLG) + fprintf(stderr, "[dprpxx: Started]"); + + /* See if using DMA to 10 memory, and set up if so */ + d->d_10mem = NULL; + if (d->d_rp->dprp_dma) { + /* Attempt to attach segment into our address space */ + char *ptr = (char *)shmat(d->d_rp->dprp_shmid, (void *)0, SHM_RND); + struct shmid_ds shmds; + + if ((int)ptr == -1) { + fprintf(stderr, "[dprpxx: shmat failed for 10 mem - %s]\n", + dp_strerror(errno)); + d->d_rp->dprp_shmid = 0; + d->d_rp->dprp_dma = FALSE; + ptr = NULL; + + } else if (shmctl(d->d_rp->dprp_shmid, IPC_STAT, &shmds)) { + fprintf(stderr, "[dprpxx: shmctl failed for 10 mem - %s]\n", + dp_strerror(errno)); + d->d_rp->dprp_shmid = 0; + d->d_rp->dprp_dma = FALSE; + ptr = NULL; + } else { + /* Set size of 10 memory we have mapped */ + d->d_10siz = shmds.shm_segsz / sizeof(w10_t); + } + + d->d_10mem = (w10_t *)ptr; /* Won, set up pointer! */ + if (ptr && DBGFLG) + fprintf(stderr, "[dprpxx: Mapped 10 mem, %ld wds]", + (long)d->d_10siz); + } + + /* Find location and size of record buffer to use */ + d->d_buff = dp_xrbuff(dp_dpxto(&d->d_dp), &d->d_blen); + + /* Set up necessary event handlers for 10-DP communication. + ** For now this is done by DPSUP. + */ + + /* Ignore TTY cruft so CTY hacking in 10 doesn't bother us */ + signal(SIGINT, SIG_IGN); /* Ignore TTY cruft */ + signal(SIGQUIT, SIG_IGN); + + /* Open disk drive initially specified, if one; initialize stuff */ + d->d_state = DPRPXX_STA_OFF; + + /* Initialize VDK code */ + if (!vdk_init(&(d->d_vdk), NULLPROC, (char *)NULL)) + return 0; + + tentorp(d); /* Start normal command/response process */ + + return 1; /* Never returns, but silence compiler */ +} + +/* RPTOTEN - Process to handle unexpected events and report them to the 10. +** Does nothing for now; later could be responsible for listening +** and responding to operations from a user disk-interface process. +*/ + +void rptoten(register struct devdk *d) +{ + register struct dpx_s *dpx; + register unsigned char *buff; + size_t max; +#if 0 + register int cnt; + int stoploop = 50; +#endif + + dpx = dp_dpxfr(&d->d_dp); /* Get ptr to from-DP comm rgn */ + buff = dp_xsbuff(dpx, &max); /* Set up buffer ptr & max count */ + + for (;;) { + +#if 0 + /* Make sure that buffer is free before clobbering it */ + dp_xswait(dpx); /* Wait until buff free */ + + /* OK, now do a blocking read on external command input! */ + if ((cnt = read(pffd, buff, max)) <= MINREQPKT) { + + if (cnt < 0 && (errno == EINTR)) /* Ignore spurious signals */ + continue; + + /* Error of some kind */ + fprintf(stderr, "dprpxx: REQPKT read = %d, ", cnt); + if (cnt < 0) { + if (--stoploop <= 0) + efatal(1, "Too many retries, aborting"); + fprintf(stderr, "errno %d = %s\r\n", + errno, dp_strerror(errno)); + } else if (cnt > 0) + fprintf(stderr, "no REQPKT data\r\n"); + else fprintf(stderr, "no REQPKT\r\n"); + + continue; /* For now... */ + } + + /* Normal packet, pass to 10 via DPC */ + /* Or simply process here, then hand up results */ + dp_xsend(dpx, DPRP_RPKT, cnt); +#endif /* 0 */ + } +} + +/* Slave Status Change - Signal attention. +** Drive came online as a result of either: +** (1) a successful soft mount request +** (2) a hard mount came online +*/ +void sscattn(register struct devdk *d) +{ + register struct dpx_s *dpx; + + dpx = dp_dpxfr(&d->d_dp); /* Get ptr to from-DP comm rgn */ + + dp_xswait(dpx); /* Wait until receiver ready */ + dp_xsend(dpx, DPRP_MOUNT, 0); /* Send note to 10! */ + +} + +void chkmntreq(register struct devdk *d) +{ + d->d_mntreq = FALSE; /* For now */ +} + +/* TENTORP - Main loop for thread handling commands from the 10. +** Reads DPC message from 10 and interprets it, returning a +** result code (and/or data). +*/ + +void tentorp(register struct devdk *d) +{ + register struct dpx_s *dpx; + register unsigned char *buff; + size_t max; + register int rcnt; + int res; + int cmd; + + + if (DBGFLG) + fprintf(stderr, "[dprpxx: in tentorp]"); + + dpx = dp_dpxto(&(d->d_dp)); /* Get ptr to "To-DP" xfer stuff */ + buff = dp_xrbuff(dpx, &max); + + for (;;) { + + /* Wait until 10 has a command for us */ + dp_xrwait(dpx); + + /* Reset some stuff for every command */ + d->d_rp->dprp_err = 0; + res = DPRP_RES_SUCC; /* Default is successful op */ + + /* Process command from 10! */ + switch (cmd = dp_xrcmd(dpx)) { + + default: + fprintf(stderr, "[dprpxx: Unknown cmd %o]\r\n", dp_xrcmd(dpx)); + res = DPRP_RES_FAIL; + break; + + case DPRP_RESET: /* Reset DP */ + /* Attempt to do complete reset */ + fprintf(stderr, "[dprpxx: Reset request]\r\n"); +#if 0 + dprp_restart(2); +#endif + break; + + case DPRP_MOUNT: /* Mount disk specified by string of N bytes */ + { + unsigned char *tmpbuf = buff+1; + int wrtf; + + dprpclear(d); + switch (buff[0]) { /* Check first char */ + case 'R': wrtf = FALSE; break; + case '*': + case 'W': wrtf = TRUE; break; + default: + fprintf(stderr, "[dprpxx: Unknown mount type \'%c\']\r\n", + buff[0]); + res = DPRP_RES_FAIL; + break; + } + if (res != DPRP_RES_FAIL) { + if (!devmount(d, (char *)tmpbuf, wrtf)) { + res = DPRP_RES_FAIL; + } + } + } + break; + + case DPRP_SEEK: + case DPRP_NOP: /* No operation */ + break; + + case DPRP_UNL: /* Unload?? (Eject?) */ + if (!devclose(d)) { /* Same as close for now */ + res = DPRP_RES_FAIL; + } + break; + + case DPRP_WRITE: /* Write N words */ + rcnt = dp_xrcnt(dpx); /* Get length to write */ + if (!devwrite(d)) { + /* Handle write error of some kind */ + res = DPRP_RES_FAIL; + } + break; + + case DPRP_READ: /* Read N words */ + if (!devread(d)) { + /* Handle read error of some kind? */ + res = DPRP_RES_FAIL; + } + break; + + case DPRP_WRDMA: /* Write N sectors into mem */ + if (!dmawrite(d)) { + /* Handle write error of some kind */ + res = DPRP_RES_FAIL; + } + break; + + case DPRP_RDDMA: /* Read N sectors into mem */ + if (!dmaread(d)) { + /* Handle read error of some kind? */ + res = DPRP_RES_FAIL; + } + break; + + } +#if 0 + dprpstat(d); /* Update most status vars */ +#endif + + /* Command done, return result and tell 10 we're done */ + dp_xrdoack(dpx, res); + } +} + +#if 0 +/* DPRPSTAT - Set shared status values +*/ +void dprpstat(d) +register struct devdk *d; +{ + register struct dprpxx_s *dprp = d->d_rp; + register struct vdk_unit *t = &d->d_vdk; + + switch (d->d_isdisk) { + case MTYP_NONE: + dprp->dprp_mol = FALSE; /* Medium online */ + dprp->dprp_wrl = FALSE; /* Write-locked */ + break; + + case MTYP_VIRT: + dprp->dprp_mol = vmt_ismounted(t); /* Medium online */ + dprp->dprp_wrl = !vmt_iswritable(t); /* Write-locked */ + dprp->dprp_err = vmt_errors(t); + break; + + default: + if (dprp->dprp_mol = d->mta_mol) { + if (d->d_state == DPRPXX_STA_OFF) { + if (DBGFLG) + fprintf(stderr, "[dprpxx: Disk came online: \"%s\"]\r\n", + d->d_path); + sscattn(d); /* Signal 10 re change */ + } + d->d_state = DPRPXX_STA_ON; + } else { + if (d->d_state == DPRPXX_STA_ON) { + if (DBGFLG) + fprintf(stderr, "[dprpxx: Disk went offline: \"%s\"]\r\n", + d->d_path); + } + d->d_state = DPRPXX_STA_OFF; + } + dprp->dprp_wrl = d->mta_wrl; + dprp->dprp_err = d->mta_err; + break; + } +} +#endif + +void dprpclear(register struct devdk *d) +{ + register struct dprpxx_s *dprp = d->d_rp; + + dprp->dprp_mol = FALSE; + dprp->dprp_wrl = FALSE; + dprp->dprp_err = 0; +} + + +/* Generic device routines (null, virtual, and real) +** Open, Close, Read, Write, Write-EOF, Write-EOT. +*/ + +/* MOUNT - must already have set up +** d_buff, d_len. +*/ +int devmount(register struct devdk *d, char *opath, int wrtf) +{ + register struct dprpxx_s *dprp = d->d_rp; + char *path; + + /* Unmount & close existing drive if any */ + if (d->d_state == DPRPXX_STA_ON) + devclose(d); + + /* Check out path argument and copy it */ + + if (!opath || !*opath) { + fprintf(stderr, "[dprpxx: Null mount path]\r\n"); + return FALSE; + } + path = malloc(strlen(opath)+1); + strcpy(path, opath); + + + /* Open the necessary files */ + + /* All these should have been set up beforehand in shared mem */ + d->d_vdk.dk_format = dprp->dprp_fmt; + strcpy(d->d_vdk.dk_devname, dprp->dprp_devname); + d->d_vdk.dk_ncyls = dprp->dprp_ncyl; + d->d_vdk.dk_ntrks = dprp->dprp_ntrk; + d->d_vdk.dk_nsecs = dprp->dprp_nsec; + d->d_vdk.dk_nwds = dprp->dprp_nwds; + if (!vdk_mount(&d->d_vdk, path, wrtf)) { + fprintf(stderr, "[dprpxx: Cannot mount device \"%s\": %s]\r\n", + path, dp_strerror(d->d_vdk.dk_err)); + free(path); + return FALSE; + } + + d->d_state = DPRPXX_STA_ON; + if (DBGFLG) + fprintf(stderr, "[dprpxx: Mounted disk device \"%s\"]\r\n", path); + + d->d_path = path; + d->d_isdisk = TRUE; + dprp->dprp_mol = TRUE; + dprp->dprp_wrl = !wrtf; + + return TRUE; +} + + +int devclose(struct devdk *d) +{ + int res = TRUE; + + if (DBGFLG && d->d_path) + fprintf(stderr, "[dprpxx: Closing \"%s\"]\r\n", d->d_path); + + if (d->d_isdisk) { + res = vdk_unmount(&d->d_vdk); /* Close real disk */ + } + + /* Force us to forget about it even if above stuff failed */ + d->d_state = DPRPXX_STA_OFF; + d->d_isdisk = FALSE; + if (d->d_path) { + free(d->d_path); + d->d_path = NULL; + } + return res; +} + +/* Read from device +** Returns 1 if read something +** Returns 0 if read nothing or error +*/ + +int devread(struct devdk *d) +{ + register struct dprpxx_s *dprp = d->d_rp; + int nsec; + + if (! d->d_isdisk) { + dprp->dprp_err = 1; + dprp->dprp_scnt = 0; + return FALSE; + } + nsec = dprp->dprp_scnt; + if (DBGFLG) + fprintf(stderr, + "[dprpxx: read daddr=%ld, buff=0x%lx, wc=%d, nsec=%d]\r\n", + (long)dprp->dprp_daddr, (long)d->d_buff, + (int)(nsec * dprp->dprp_nwds), nsec); + + dprp->dprp_scnt = vdk_read(&d->d_vdk, + (w10_t *)d->d_buff, /* Word buffer loc */ + (int32) dprp->dprp_daddr, /* Disk addr (sectors) */ + nsec); /* # sectors */ + if ((dprp->dprp_err = d->d_vdk.dk_err) + || (dprp->dprp_scnt != nsec)) { + fprintf(stderr, "[dprpxx: read error on %s: %s]\r\n", + d->d_vdk.dk_filename, dp_strerror(d->d_vdk.dk_err)); + return FALSE; + } + return TRUE; +} + +/* Write to device. +*/ +int devwrite(struct devdk *d) +{ + register struct dprpxx_s *dprp = d->d_rp; + int nsec; + + if (! d->d_isdisk) { + dprp->dprp_err = 1; + dprp->dprp_scnt = 0; + return FALSE; + } + nsec = dprp->dprp_scnt; + if (DBGFLG) + fprintf(stderr, + "[dprpxx: write daddr=%ld, buff=0x%lx, wc=%d, nsec=%d]\r\n", + (long)dprp->dprp_daddr, (long)d->d_buff, + (int)(nsec * dprp->dprp_nwds), nsec); + + dprp->dprp_scnt = vdk_write(&d->d_vdk, + (w10_t *)d->d_buff, /* Word buffer loc */ + (int32) dprp->dprp_daddr, /* Disk addr (sectors) */ + nsec); /* # sectors */ + if ((dprp->dprp_err = d->d_vdk.dk_err) + || (dprp->dprp_scnt != nsec)) { + fprintf(stderr, "[dprpxx: write error on %s: %s]\r\n", + d->d_vdk.dk_filename, dp_strerror(d->d_vdk.dk_err)); + return FALSE; + } + return TRUE; +} + +/* Read DMA sectors from device +** Returns 1 if read something +** Returns 0 if read nothing or error +*/ + +int dmaread(register struct devdk *d) +{ + register struct dprpxx_s *dprp = d->d_rp; + int nsec; + int res; + + if (!d->d_isdisk) { + dprp->dprp_err = 1; + dprp->dprp_scnt = 0; + return FALSE; + } + if (!d->d_10mem) { + fprintf(stderr, "[dprpxx: Read DMA unsupported!]\r\n"); + dprp->dprp_err = 1; + dprp->dprp_scnt = 0; + return FALSE; + } + if (dprp->dprp_phyadr >= d->d_10siz) { + fprintf(stderr, "[dprpxx: Non-ex phys addr %#lo]\r\n", + (long)dprp->dprp_phyadr); + dprp->dprp_err = 1; + dprp->dprp_scnt = 0; + return FALSE; + } + + nsec = dprp->dprp_scnt; + + if (DBGFLG) + fprintf(stderr, + "[dprpxx: read daddr=%ld, mem=%#lo, wc=%d, nsec=%d]\r\n", + (long)dprp->dprp_daddr, (long)dprp->dprp_phyadr, + (int)(nsec * dprp->dprp_nwds), nsec); + + res = vdk_read(&d->d_vdk, + d->d_10mem + + dprp->dprp_phyadr, /* Word buffer loc */ + (int32) dprp->dprp_daddr, /* Disk addr (sectors) */ + nsec); /* # sectors */ + + dprp->dprp_scnt = res; + dprp->dprp_err = d->d_vdk.dk_err; + + if (res == nsec && !dprp->dprp_err) + return TRUE; + + fprintf(stderr, "[dprpxx: read error on %s: %s]\r\n", + d->d_vdk.dk_filename, dp_strerror(d->d_vdk.dk_err)); + return FALSE; +} + +/* Write DMA sectors to device. +*/ +int dmawrite(register struct devdk *d) +{ + register struct dprpxx_s *dprp = d->d_rp; + int nsec; + int res; + + if (!d->d_isdisk) { + dprp->dprp_err = 1; + dprp->dprp_scnt = 0; + return FALSE; + } + if (!d->d_10mem) { + fprintf(stderr, "[dprpxx: Write DMA unsupported!]\r\n"); + dprp->dprp_err = 1; + dprp->dprp_scnt = 0; + return FALSE; + } + if (dprp->dprp_phyadr >= d->d_10siz) { + fprintf(stderr, "[dprpxx: Non-ex phys addr %#lo]\r\n", + (long)dprp->dprp_phyadr); + dprp->dprp_err = 1; + dprp->dprp_scnt = 0; + return FALSE; + } + + nsec = dprp->dprp_scnt; + + if (DBGFLG) + fprintf(stderr, + "[dprpxx: write daddr=%ld, mem=%#lo, wc=%d, nsec=%d]\r\n", + (long)dprp->dprp_daddr, (long)dprp->dprp_phyadr, + (int)(nsec * dprp->dprp_nwds), nsec); + + res = vdk_write(&d->d_vdk, + d->d_10mem + + dprp->dprp_phyadr, /* Word buffer loc */ + (int32) dprp->dprp_daddr, /* Disk addr (sectors) */ + nsec); /* # sectors */ + + dprp->dprp_scnt = res; + dprp->dprp_err = d->d_vdk.dk_err; + + if (res == nsec && !dprp->dprp_err) + return TRUE; + + fprintf(stderr, "[dprpxx: write error on %s: %s]\r\n", + d->d_vdk.dk_filename, dp_strerror(d->d_vdk.dk_err)); + return FALSE; +} + +#if 0 + +/* Disk handling routines +*/ + + +int os_dkopen(d, path, wrtf) +struct devdk *d; +char *path; +{ + + d->d_rp->dprp_mol = 0; /* Plus general state */ + d->d_rp->dprp_wrl = 0; + +#if CENV_SYS_UNIX + { + int fd; + + fd = open(path, (wrtf ? O_RDWR : O_RDONLY), 0600); + if (fd < 0) { + + /* Check out failures. Maybe later allow ENOENT or EINTR? */ + switch (errno) { + default: + d->d_rp->dprp_err = errno; + break; + } + return FALSE; + } + d->d_fd = fd; + d->d_rp->dprp_mol = TRUE; + d->d_rp->dprp_wrl = !wrtf; + } + return TRUE; +#else + return FALSE; +#endif +} + +int os_dkclose(d) +struct devdk *d; +{ + d->d_rp->dprp_mol = 0; + d->d_rp->dprp_wrl = 0; + +#if CENV_SYS_UNIX + return close(d->d_fd); +#else + return TRUE; +#endif +} + +os_dkread(d) +register struct devdk *d; +{ +#if CENV_SYS_UNIX + /* Don't try to support retries here. If OS doesn't do it, there isn't + ** a whole lot we can do better. (But then again, it's Un*x, so...) + */ + unsigned int res; + + for (;;) { + switch (res = read(d->d_fd, d->d_buff, d->d_blen)) { + case 0: /* Diskmark */ + d->d_rp->dprp_eof = TRUE; + return TRUE; + + default: /* Assume record read */ + d->d_rp->dprp_frms = res; + return TRUE; + + case -1: /* Error */ + if (errno = EINTR) + continue; + if (errno = ENOSPC) { /* OSF/1 returns this for EOT */ + d->d_rp->dprp_eot = TRUE; + return TRUE; + } + d->d_rp->dprp_err++; + fprintf(stderr, "[dprpxx: Disk read error: %s\r\n", + dp_strerror(errno)); + os_dkshow(d, stderr); /* Show full status */ + fprintf(stderr, "]\r\n"); + return FALSE; + } + } + return FALSE; +#endif /* CENV_SYS_UNIX */ + + return 0; /* For now */ +} + +int os_dkwrite(d, buff, len) +register struct devdk *d; +register char *buff; +size_t len; +{ +#if CENV_SYS_UNIX + + unsigned int ret; + + + for (;;) { + if ((ret = write(d->d_fd, buff, len)) == len) { + d->d_rp->dprp_frms = ret; /* Won! */ + return TRUE; + } + + /* Error of some kind */ + if (ret == -1) { + if (errno == EINTR) + continue; + if (errno == ENOSPC) /* Reached EOT? */ + d->d_rp->dprp_eot = TRUE; + else + os_dkstate(d); /* Dunno, try general status rtn */ + + fprintf(stderr, "[dprpxx: write err: %s]\r\n", dp_strerror(errno)); + os_dkshow(d, stderr); /* Show full status */ + d->d_rp->dprp_err++; + return FALSE; + } + + fprintf(stderr, "[dprpxx: write trunc: %ld => %d]\r\n", (long)len, ret); + d->d_rp->dprp_frms = ret; + return TRUE; /* Some kind of error, but let caller figure out */ + } +#endif /* CENV_SYS_UNIX */ + + return FALSE; /* For now */ +} + +#endif /* 0 */ + +/* General-purpose System-level I/O. +** It is intended that this level of IO be in some sense the fastest +** or most efficient way to interact with the host OS, as opposed to +** the more portable stdio interface. +*/ + +int +os_fdopen(osfd_t *afd, char *file, char *modes) +{ +#if CENV_SYS_UNIX || CENV_SYS_MAC + int flags = 0; + if (!afd) return FALSE; + for (; modes && *modes; ++modes) switch (*modes) { + case 'r': flags |= O_RDONLY; break; /* Yes I know it's 0 */ + case 'w': flags |= O_WRONLY; break; + case '+': flags |= O_RDWR; break; + case 'a': flags |= O_APPEND; break; + case 'b': /* Binary */ +# if CENV_SYS_MAC + flags |= O_BINARY; +# endif + break; + case 'c': flags |= O_CREAT; break; + /* Ignore unknown chars for now */ + } +# if CENV_SYS_MAC + if ((*afd = open(file, flags)) < 0) +# else + if ((*afd = open(file, flags, 0666)) < 0) +# endif + return FALSE; + return TRUE; + +#elif CENV_SYS_MOONMAC + Boolean create = FALSE, append = FALSE; + short refnum; + OSErr err; + char pascal_string[256]; + + if (!afd) return FALSE; + for (; modes && *modes; ++modes) switch (*modes) { + case 'a': append = TRUE; break; + case 'c': create = TRUE; break; + /* Ignore read/write and unknown chars for now */ + } + pascal_string[0] = strlen(file); + BlockMove(file, &pascal_string[1], pascal_string[0]); + if (create) Create(pascal_string, 0, 'KH10', 'TEXT'); + err = FSOpen(pascal_string, 0, &refnum); + *afd = err; + if (err) return FALSE; + *afd = refnum; + return TRUE; +#endif +} + +int +os_fdclose(osfd_t fd) +{ +#if CENV_SYS_UNIX || CENV_SYS_MAC + return close(fd) != -1; +#else + return 0; +#endif +} + +int +os_fdseek(osfd_t fd, osdaddr_t addr) +{ +#if CENV_SYS_UNIX + return lseek(fd, addr, L_SET) != -1; +#elif CENV_SYS_MAC || CENV_SYS_MOONMAC + return lseek(fd, addr, SEEK_SET) != -1; +#else + return 0; +#endif +} + +int +os_fdread(osfd_t fd, char *buf, size_t len, size_t *ares) +{ +#if CENV_SYS_UNIX || CENV_SYS_MOONMAC + register int res = read(fd, buf, len); + if (res < 0) { + if (ares) *ares = 0; + return FALSE; + } +#elif CENV_SYS_MAC + /* This is actually generic code for any system supporting unix-like + ** calls with a 16-bit integer count interface. + */ + register size_t res = 0; + register unsigned int scnt, sres = 0; + + while (len) { + scnt = len > (1<<14) ? (1<<14) : len; /* 16-bit count each whack */ + if ((sres = read(fd, buf, cnt)) != scnt) { + if (sres == -1) { /* If didn't complete, check for err */ + if (ares) *ares = res; /* Error, but may have read stuff */ + return FALSE; + } + res += sres; /* No error, just update count */ + break; /* and return successfully */ + } + res += sres; + len -= sres; + buf += sres; + } +#endif + if (ares) *ares = res; + return TRUE; +} + +int +os_fdwrite(osfd_t fd, char *buf, size_t len, size_t *ares) +{ +#if CENV_SYS_UNIX || CENV_SYS_MOONMAC + register int res = write(fd, buf, len); + if (res < 0) { + if (ares) *ares = 0; + return FALSE; + } +#elif CENV_SYS_MAC + /* This is actually generic code for any system supporting unix-like + ** calls with a 16-bit integer count interface. + */ + register size_t res = 0; + register unsigned int scnt, sres = 0; + + while (len) { + scnt = len > (1<<14) ? (1<<14) : len; /* 16-bit count each whack */ + if ((sres = write(fd, buf, cnt)) != scnt) { + if (sres == -1) { /* If didn't complete, check for err */ + if (ares) *ares = res; /* Error, but may have written stuff */ + return FALSE; + } + res += sres; /* No error, just update count */ + break; /* and return successfully */ + } + res += sres; + len -= sres; + buf += sres; + } +#endif + if (ares) *ares = res; + return TRUE; +} diff --git a/src/dprpxx.h b/src/dprpxx.h new file mode 100644 index 0000000..bc09084 --- /dev/null +++ b/src/dprpxx.h @@ -0,0 +1,104 @@ +/* DPRPXX.H - Device sub-Process defs for RPxx +*/ +/* $Id: dprpxx.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1994, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dprpxx.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#ifndef DPRPXX_INCLUDED +#define DPRPXX_INCLUDED 1 + +#ifdef RCSID + RCSID(dprpxx_h,"$Id: dprpxx.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +#ifndef DPSUP_INCLUDED +# include "dpsup.h" +#endif + +#ifndef DPRP_NSECS_MAX /* Max # sectors for single I/O operation */ +# define DPRP_NSECS_MAX 4 /* 4*128 = 512 wds */ +#endif + +/* DPRPXX-specific stuff */ + +struct dprpxx_s { + struct dpc_s dprp_dpc; /* Standard DPC portion */ + int dprp_dma; /* TRUE if want to use DMA */ + int dprp_shmid; /* SHM ID for 10-memory, if DMA allowed */ + int dprp_debug; /* TRUE if want subproc debug output */ + + int dprp_res; /* Operation result */ + int dprp_err; /* Non-zero if error */ + unsigned long dprp_scnt; /* # sectors xferred */ + unsigned long dprp_daddr; /* Disk address as # sectors */ + uint32 dprp_phyadr; /* Memory word address for DMA */ + + /* Unused, maybe later */ + int dprp_cyl, /* Desired cyl */ + dprp_trk, /* and track */ + dprp_sec; /* and sector */ + + /* Disk format - set by 10, read by DP */ + int dprp_fmt; + unsigned long dprp_totsec; + int dprp_ncyl; + int dprp_ntrk; + int dprp_nsec; + int dprp_nwds; + char dprp_devname[16]; + + /* Disk status - set by DP. Not really used. */ + int dprp_mol; + int dprp_wrl; +}; + +#define DPRP_RES_FAIL 0 +#define DPRP_RES_SUCC 1 + + +/* Commands to and from DP and KLH10 RPXX driver */ + +enum { + DPRP_RESET=0, /* 0 - Reset DP */ + DPRP_MOUNT, /* Mount R/W disk specified by string of N bytes */ + DPRP_ROMNT, /* Mount RO disk specified by string of N bytes */ + + DPRP_NOP, /* No operation */ + DPRP_UNL, /* Unload (go offline) */ + DPRP_SEEK, /* Seek (basically a nop) */ + + DPRP_READ, /* Read N words */ + DPRP_WRITE, /* Write N words */ + + DPRP_RDSEC, /* Read N sectors */ + DPRP_WRSEC, /* Write N sectors */ + + DPRP_RDDMA, /* Read N sectors directly into memory */ + DPRP_WRDMA, /* Write N sectors directly from memory */ + + DPRP_RDDCH, /* Read N words directly to data channel */ + DPRP_WRDCH, /* Write N words directly from data channel */ + + DPRP_SNS /* Sense? */ +}; + + +#endif /* ifndef DPRPXX_INCLUDED */ diff --git a/src/dpsup.c b/src/dpsup.c new file mode 100644 index 0000000..736967a --- /dev/null +++ b/src/dpsup.c @@ -0,0 +1,747 @@ +/* DPSUP.C - Device sub-Process Support facilities (OSD) for KLH10 +*/ +/* $Id: dpsup.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1994, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dpsup.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#include "klh10.h" /* Get config params */ + +#if !KLH10_DEV_DP && CENV_SYS_DECOSF + /* Stupid gubbish needed to prevent OSF/1 AXP compiler from + ** halting merely because compiled file is empty! + */ +static int decosfcclossage; +#endif + +#if KLH10_DEV_DP /* Moby conditional for entire file */ + +#include +#include /* For strerror() if present */ + +#include "dpsup.h" + +#if CENV_SYS_DECOSF || CENV_SYS_SUN || CENV_SYS_SOLARIS || CENV_SYS_XBSD || CENV_SYS_LINUX +# include +# include /* SysV stuff */ +# include /* SysV stuff */ +# include +# include +# include +# include +# if CENV_SYS_SUN || CENV_SYS_SOLARIS +# define SIGMAX MAXSIG /* Different wording on Sun */ +# elif CENV_SYS_FREEBSD +# define SIGMAX NSIG +# elif CENV_SYS_NETBSD || CENV_SYS_LINUX +# define SIGMAX _NSIG +# endif +#endif /* CENV_SYS_DECOSF || CENV_SYS_SUN || CENV_SYS_SOLARIS || CENV_SYS_XBSD || CENV_SYS_LINUX */ + +#ifdef RCSID + RCSID(dpsup_c,"$Id: dpsup.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +static int dp_cxinit(struct dpc_s *, int, int, int, size_t, size_t); + +/* DP_INIT - Called from superior (KLH10) to initialize device subprocess +** context and shared memory area. +** +** Note that the shared area is divided into three parts in this +** order: +** (1) DPC structure (of size dpcsiz) +** (2) Output buffer 10->DP (of size outsiz) +** (3) Input buffer 10<-DP (of size insiz) +** +** *** THE READ-REVERSE CODE FOR TM02/TM03 DEPENDS ON THIS ORDERING! *** +** (Check references to dptm_revpad) +*/ +int dp_init(register struct dp_s *dp, size_t dpcsiz, + int intyp, int inarg, size_t insiz, + int outtyp, int outarg, size_t outsiz) +{ + register size_t totsiz; + register int adj; + register key_t shmid; + register struct dpc_s *dpc; + + dp->dp_type = 0; /* Ensure cleared in case bomb out */ + dp->dp_shmid = -1; + dp->dp_adr = NULL; + dp->dp_chpid = 0; + + /* Ensure all sizes are aligned to satisfy maximum restrictions */ + if (adj = (dpcsiz % sizeof(double))) + dpcsiz += sizeof(double) - adj; + if (adj = (insiz % sizeof(double))) + insiz += sizeof(double) - adj; + if (adj = (outsiz % sizeof(double))) + outsiz += sizeof(double) - adj; + + totsiz = dpcsiz + insiz + outsiz; + + /* Create shared mem segment of given size */ + if (dpcsiz < sizeof(struct dpc_s)) { /* Ensure big enough for std stuff */ + fprintf(stderr, "[dp_init: dpcsiz %ld]\r\n", (long)dpcsiz); + return FALSE; + } + + /* Create a shared mem seg. Set perms to owner-only RW. */ + if ((shmid = shmget(IPC_PRIVATE, (u_int)totsiz, 0600)) == -1) { + fprintf(stderr, "[dp_init: shmget failed - %d]\r\n", errno); + return FALSE; + } + + /* Attempt to attach segment into our address space */ + dpc = (struct dpc_s *)shmat(shmid, (void *)0, SHM_RND); + if ((int)dpc == -1) { + shmctl(shmid, IPC_RMID, (struct shmid_ds *)NULL); + fprintf(stderr, "[dp_init: shmat failed - %d]\r\n", errno); + return FALSE; + } + + /* Won, init the shared DPC struct */ + memset((char *)dpc, 0, totsiz); + strncpy(dpc->dpc_magic, DPC_MAGIC, sizeof(dpc->dpc_magic)); + dpc->dpc_fmtver = DPSUP_VERSION; /* Set to current version */ + + /* Set up output then input xfer stuff */ + if (!dp_cxinit(dpc, 1, outtyp, outarg, dpcsiz, outsiz) + || !dp_cxinit(dpc, 0, intyp, inarg, dpcsiz+outsiz, insiz)) { + + shmdt((caddr_t)dpc); /* Detach attached segment */ + shmctl(shmid, IPC_RMID, (struct shmid_ds *)NULL); + fprintf(stderr, "[dp_init: xinit failed]\r\n"); + return FALSE; + } + + /* Finally init the DP struct itself */ + dp->dp_type = DP_XT_MSIG; + dp->dp_adr = dpc; + dp->dp_shmid = shmid; + + return TRUE; +} + +static int dp_cxinit(register struct dpc_s *dpc, + int dir, int type, int arg, size_t off, size_t siz) +{ + register struct dpx_s *dx; + + if (type != DP_XT_MSIG) + return FALSE; /* Unknown xfer type */ + + /* Arg is signal # to use for this direction */ + if (arg <= 0 || SIGMAX <= arg) + return FALSE; /* Bad signal # */ + + if (dir) { + dx = &dpc->dpc_todp; /* Output to DP */ + dx->dpx_dontyp = type; + dx->dpx_donflg = 0; + dx->dpx_donsig = arg; /* Say how to ack sender (10) */ + dx->dpx_donpid = getpid(); + dx->dpx_sbuf = (unsigned char *)dpc + off; + + } else { + dx = &dpc->dpc_frdp; /* Input from DP */ + dx->dpx_waktyp = type; + dx->dpx_wakflg = 0; + dx->dpx_waksig = arg; /* Say how to wakeup rcpt (10) */ + dx->dpx_wakpid = getpid(); + dx->dpx_rbuf = (unsigned char *)dpc + off; + } + + dx->dpx_type = type; /* Is this necessary? */ + + dx->dpx_len = siz; /* Size of buffer */ + dx->dpx_off = off; /* Offset of buffer from start of seg */ + + dx->dpx_rdyf = 0; /* No data */ + dx->dpx_cmd = 0; /* No command */ + + return TRUE; +} + + +int dp_start(register struct dp_s *dp, char *prog) +{ + int err; + int pid; + char idbuf[20]; + sigset_t allmask, oldmask; + int debug = (dp->dp_adr ? dp->dp_adr->dpc_debug : 0); + + /* Set up args for DP proc */ + sprintf(idbuf, "-DPM:%ld", (long)dp->dp_shmid); + + /* Check xct access for program */ + if ((err = access(prog, X_OK)) < 0) { + fprintf(stderr, "[dp_exec: Cannot access \"%s\" - %s]\r\n", + prog ? prog : "(nullptr)", dp_strerror(err)); + return FALSE; + } + + /* Block all signals momentarily so new process isn't killed + ** by asynch signals. + */ + sigfillset(&allmask); + (void) sigprocmask(SIG_SETMASK, &allmask, &oldmask); + + if (debug) + fprintf(stderr, "[dp_start: Forking...]"); + if ((pid = fork()) < 0) { + /* Cannot fork */ + fprintf(stderr, "[dp_exec: Cannot fork for \"%s\" - %s]\r\n", + prog ? prog : "(nullptr)", dp_strerror(-1)); + (void) sigprocmask(SIG_SETMASK, &oldmask, (sigset_t *)NULL); + return FALSE; + } + if (debug) + fprintf(stderr, "[dp_start: Forked %d]\r\n", pid); + if (pid == 0) { + /* We're the child process, start up specified program */ + if (debug) { + fprintf(stderr, + "[dp_start: execing \"%s\" \"%s\" \"-debug\"]\r\n", + prog, idbuf); + execl(prog, prog, idbuf, "-debug", (char *)NULL); + } else + execl(prog, prog, idbuf, (char *)NULL); + + fprintf(stderr, "[dp_exec: execl failed - %s]\r\n", + dp_strerror(-1)); + _exit(1); /* Not exit(), to avoid muckage */ + } + dp->dp_chpid = pid; /* Parent, remember child's PID */ + +#if 0 /* This has been needed sometimes to get child going!!! */ + { + int i; + for (i = (1<<28); --i > 0;); /* Should take 8 sec or so */ + } +#endif + (void) sigprocmask(SIG_SETMASK, &oldmask, (sigset_t *)NULL); + + return TRUE; +} + + +int dp_reset(register struct dp_s *dp) +{ + +#if 0 + if (dp->dp_chpid) { + kill(dp->dp_chpid, SIGKILL); + } +#endif + return 1; +} + +/* DP_TERM - Terminate entire subproc hackery - opposite of dp_init. +*/ +int dp_term(register struct dp_s *dp, int timeout) +{ + dp_stop(dp, timeout); /* Stop, kill subproc */ + + /* Try to kill shared mem segment */ + if (dp->dp_type == DP_XT_MSIG) { + shmdt((caddr_t)(dp->dp_adr)); /* Detach attached segment */ + shmctl(dp->dp_shmid, IPC_RMID, /* then try to flush it */ + (struct shmid_ds *)NULL); + dp->dp_adr = NULL; + dp->dp_shmid = 0; + dp->dp_type = 0; + } + return 1; +} + +static int dp_killchild(pid_t pid, int timeout) +{ + int status, res, cnt; + + kill(pid, SIGKILL); + cnt = timeout ? timeout : 1; + for (; --cnt >= 0;) { + res = waitpid(pid, &status, WNOHANG); + if (res == -1) { + if (errno == EINTR) continue; + return (errno == ECHILD); /* TRUE if won */ + } + if (res != 0) /* Nonzero result means got stopped proc */ + return 1; /* Won! */ + if (cnt > 0) + dp_sleep(1); /* Urgh, wait a bit, big crock */ + } + return 0; /* Timed out, failed */ +} + +int dp_stop(register struct dp_s *dp, int timeout) +{ + pid_t pid, pid2; + + switch (dp->dp_type) { + case DP_XT_MSIG: + if (pid = dp->dp_chpid) { + (void) dp_killchild(pid, timeout); + + /* For now, flush pid even if didn't find it when waited. */ + dp->dp_chpid = 0; + } + + /* Check for presence of 2nd child */ + if ((pid2 = dp->dp_adr->dpc_frdp.dpx_donpid) + && (pid != pid2)) { + (void) dp_killchild(pid2, timeout); + + /* For now, flush pid even if didn't find it when waited. */ + dp->dp_adr->dpc_frdp.dpx_donpid = 0; + } + break; + } + + /* Clear up all xfer stuff from this side */ + return 1; +} + +/* Called from subprocess (dp) */ + +/* + The current method for signalling the subproc uses signals +combined with a "ready" flag. The ready flag is the true state indicator; +the signal merely serves as a method of waking up the subproc in case it +is run-blocked, either waiting for the flag to change --OR-- for some +system call to complete. + + Normally the subproc runs with the wakeup signal masked (blocked), +and only permits it to interrupt when it makes a sigpause(0) call as part +of the flag check-and-wait procedure. In this respect the signal amounts +to little more than a semaphore. + However, the subproc also has the option of unmasking the signal +so that it can permit selected system calls to be interrupted. For this +reason, the sigaction which sets up the signal configures it to NOT +restart system calls. + The latter feature (along with avoiding the cleanup problems for +SYSV semaphores) is why signals are used instead of OS semaphores. + +*/ + +static void dp_subsighan(int); +static int dp_signal(int sig, void (*func)(int)); + +int dp_main(register struct dp_s *dp, int argc, char **argv) +{ + long shmarg; + register struct dpc_s *dpc; + register struct dpx_s *dpx; + int tosig, frsig; + sigset_t mask; + + if ((argc < 2) || (strncmp(argv[1], "-DPM:", 5) != 0)) { + fprintf(stderr, "[%s: need -DPM: arg]\r\n", + (argc > 0 ? argv[0] : "(?) dp_main")); + return 0; + } + + if (1 != sscanf(&argv[1][5], "%ld", &shmarg)) { + fprintf(stderr, "[%s: Couldn't parse \"%s\"]\r\n", + argv[0], argv[1]); + return 0; + } + + /* Got shmid for segment from our parent, try attaching it! */ + dpc = (struct dpc_s *)shmat((int)shmarg, (void *)NULL, SHM_RND); + if ((int)dpc == -1) { + fprintf(stderr, "[%s: Couldn't attach shmid 0x%lx]\r\n", + argv[0], shmarg); + return 0; + } + + /* Verify that we got a DP memory structure */ + if (strncmp(dpc->dpc_magic, DPC_MAGIC, sizeof(dpc->dpc_magic)) != 0) { + fprintf(stderr, "[%s: Invalid DPM seg %ld - bad magic ID]\r\n", + argv[0], (long)shmarg); + return 0; + } + + /* Verify what format the superior expects to be using. + Should this ignore patch revs? + */ + switch (dpc->dpc_fmtver) { + case DPSUP_VERSION: + break; + default: + fprintf(stderr, + "[%s: Incompatible DP versions - sup %d.%d.%d, sub %d.%d.%d]\r\n", + argv[0], + DPC_GV_MAJ(dpc->dpc_fmtver), + DPC_GV_MIN(dpc->dpc_fmtver), + DPC_GV_PAT(dpc->dpc_fmtver), + DPC_GV_MAJ(DPSUP_VERSION), + DPC_GV_MIN(DPSUP_VERSION), + DPC_GV_PAT(DPSUP_VERSION)); + + return 0; + } + + /* Hurray, we're winning... set up our stuff */ + dp->dp_type = DP_XT_MSIG; /* Should set this differently later */ + dp->dp_adr = dpc; + dp->dp_shmid = shmarg; + + /* Set up I/O to 10 - assume DP_XT_MSIG for now */ + tosig = frsig = SIGURG; /* Set up signal(s) to use */ + sigemptyset(&mask); + sigaddset(&mask, tosig); /* and combined mask */ + sigaddset(&mask, frsig); +#if 1 + sigprocmask(SIG_BLOCK, &mask, (sigset_t *)NULL); /* Ensure blocked */ +#endif + /* Safe now if superior jumps gun */ + + dpx = &dp->dp_adr->dpc_frdp; /* Output from DP to 10 */ + dpx->dpx_dontyp = DP_XT_MSIG; /* Say how to ack sender (dp) */ + dpx->dpx_donsig = frsig; /* Use this signal # */ + sigemptyset(&dpx->dpx_donmsk); /* Set corresponding mask bit */ + sigaddset(&dpx->dpx_donmsk, dpx->dpx_donsig); + dpx->dpx_donpid = getpid(); + dpx->dpx_sbuf = (unsigned char *)dpc + dpx->dpx_off; + + dpx = &dp->dp_adr->dpc_todp; /* Input to DP from 10 */ + dpx->dpx_waktyp = DP_XT_MSIG; + dpx->dpx_waksig = tosig; /* Say how to wakeup rcpt (dp) */ + sigemptyset(&dpx->dpx_wakmsk); /* Set corresponding mask bit */ + sigaddset(&dpx->dpx_wakmsk, dpx->dpx_waksig); + dpx->dpx_wakpid = getpid(); + dpx->dpx_rbuf = (unsigned char *)dpc + dpx->dpx_off; + + if (dp_signal(frsig, dp_subsighan) == -1) { + fprintf(stderr, "[%s: Couldn't set signal handler]\r\n", argv[0]); + return 0; + } + if (tosig != frsig /* Do second if necessary */ + && (dp_signal(tosig, dp_subsighan) == -1)) { + fprintf(stderr, "[%s: Couldn't set signal handler]\r\n", argv[0]); + return 0; + } + +#if 1 + /* Paranoia: check for pending signals before unblocking */ + { + int sig; + int npend = 0; + sigset_t pendmask; + sigpending(&pendmask); + for (sig = 1; sig < SIGMAX; sig++) { + if (sigismember(&pendmask, sig)) { + if (npend++ == 0) + fprintf(stderr, "[%s: WARNING! sigpend %d", argv[0], sig); + else + fprintf(stderr, ", %d", sig); + } + } + if (npend) + fprintf(stderr, "]\r\n"); + } +#endif + + /* Unblock all but the DP sigs. Instead of restoring mask as it was + ** at start of call, this actually CLEARS everything (except the DP sigs), + ** in order to balance the suspend-everything action of dp_start for + ** the child fork. + */ + sigprocmask(SIG_SETMASK, &mask, (sigset_t *)NULL); + + /* Finally, check new MEMLOCK flag to see if superior wants + ** us to propagate a memory-locked condition. + */ + if (dpc->dpc_flags & DPCF_MEMLOCK) { + /* Can only lock mem if superuser, but don't bother checking + ** beforehand - only warn if an error isn't EPERM and hence is + ** unusual. + */ +#if CENV_SYS_DECOSF || CENV_SYS_SOLARIS || CENV_SYS_LINUX + if (mlockall(MCL_CURRENT|MCL_FUTURE) != 0) { + if (errno != EPERM) + fprintf(stderr, "[%s: mlockall failed - %s]\r\n", + argv[0], dp_strerror(-1)); + } +#endif + } + + return 1; +} + +void dp_exit(register struct dp_s *dp, int res) +{ + if (dp->dp_chpid) { + kill(dp->dp_chpid, SIGKILL); + /* Perhaps later wait for that specific child */ + } + exit(res); +} + +static void dp_subsighan(int junk) +{ + /* Do nothing -- call merely breaks out of sigpause(0) */ +} + +/* Called from both KLH10 and device subprocess */ + +int dp_xstest(register struct dpx_s *dx) /* TRUE if can send */ +{ + switch (dx->dpx_type) { + case DP_XT_MSIG: + return dp_xtmsig_stest(dx); + } + return FALSE; +} + +void dp_xsblock(register struct dpx_s *dx) /* Block for a later test */ +{ + switch (dx->dpx_type) { + case DP_XT_MSIG: + dp_xtmsig_sblock(dx); + return; + } + return; +} + +int dp_xswait(register struct dpx_s *dx) /* Wait until can send */ +{ + switch (dx->dpx_type) { + case DP_XT_MSIG: + dp_xtmsig_swait(dx); + return TRUE; + } + return FALSE; +} + + +unsigned char *dp_xsbuff(register struct dpx_s *dx, + register size_t *asiz) /* Get buffer for send data */ +{ + switch (dx->dpx_type) { + case DP_XT_MSIG: + return dp_xtmsig_sbuff(dx, asiz); + } + if (asiz) + *asiz = 0; + return NULL; +} + +void dp_xswake(register struct dpx_s *dx) /* Send; say message ready */ +{ + switch (dx->dpx_type) { + case DP_XT_MSIG: + dp_xtmsig_swake(dx); + return; + } +} + +void dp_xsend(register struct dpx_s *dx, + int cmd, int cnt) /* Send cmd and data */ +{ + switch (dx->dpx_type) { + case DP_XT_MSIG: + dp_xtmsig_send(dx, cmd, cnt); + return; + } +} + + +int dp_xrtest(register struct dpx_s *dx) /* TRUE if can receive */ +{ + switch (dx->dpx_type) { + case DP_XT_MSIG: + return dp_xtmsig_rtest(dx); + } + return FALSE; +} + +void dp_xrblock(register struct dpx_s *dx) /* Block for a later test */ +{ + switch (dx->dpx_type) { + case DP_XT_MSIG: + dp_xtmsig_rblock(dx); + return; + } + return; +} + + +int dp_xrwait(register struct dpx_s *dx) /* Wait until can definitely recv */ +{ + switch (dx->dpx_type) { + case DP_XT_MSIG: + dp_xtmsig_rwait(dx); + return TRUE; + } + return FALSE; +} + + +unsigned char *dp_xrbuff(register struct dpx_s *dx, + register size_t *asiz) /* Get buffer for recv data */ +{ + switch (dx->dpx_type) { + case DP_XT_MSIG: + return dp_xtmsig_rbuff(dx, asiz); + } + if (asiz) + *asiz = 0; + return NULL; +} + +void dp_xrdone(register struct dpx_s *dx) /* Done, ready for next msg */ +{ + switch (dx->dpx_type) { + case DP_XT_MSIG: + dp_xtmsig_rdone(dx); + return; + } +} + +void dp_xrdoack(register struct dpx_s *dx, /* Done, ready for next msg */ + int res) +{ + switch (dx->dpx_type) { + case DP_XT_MSIG: + dp_xtmsig_rdoack(dx, res); + return; + } +} + +int dp_xrcmd(register struct dpx_s *dx) /* Get command */ +{ + switch (dx->dpx_type) { + case DP_XT_MSIG: + return dp_xtmsig_rcmd(dx); + } + return 0; +} + +int dp_xrcnt(register struct dpx_s *dx) /* Get data count */ +{ + switch (dx->dpx_type) { + case DP_XT_MSIG: + return dp_xtmsig_rcnt(dx); + } + return 0; +} + +/* Same as os_strerror() from osdsup.c, put here to avoid having to +** grab the entire OSDSUP package when being built for DP procs. +*/ + +char * +dp_strerror(int err) +{ + if (err == -1 && errno != err) + return dp_strerror(errno); +#if CENV_SYSF_STRERROR + return strerror(err); +#else +# if CENV_SYS_UNIX + { +# if !CENV_SYS_XBSD /* Already in signal.h */ + extern int sys_nerr; + extern char *sys_errlist[]; +# endif + if (0 < err && err <= sys_nerr) + return (char *)sys_errlist[err]; + } +# endif + if (err == 0) + return "No error"; + else { + static char ebuf[30]; + sprintf(ebuf, "Unknown-error-%d", err); + return ebuf; + } +#endif /* !CENV_SYSF_STRERROR */ +} + + +/* Likewise copied from OSDSUP.C */ + +static int +dp_signal(int sig, void (*func)(int)) +{ +#if CENV_SYSF_SIGSET + struct sigaction act, oact; + + act.sa_handler = func; + act.sa_flags = 0 /*SA_RESTART*/; /* Do *NOT* ask for restart! */ + sigemptyset(&act.sa_mask); + sigaddset(&act.sa_mask, sig); /* Suspend this sig during handler */ + return sigaction(sig, &act, &oact); +#elif CENV_SYS_BSD + /* If really BSD, probably should use sigvec instead */ + return (signal(sig, func) == (void (*)())-1) ? -1 : 0; +#else + *** ERROR *** need signal support +#endif +} + +void +dp_sigwait(void) +{ + sigset_t nomsk; + + sigemptyset(&nomsk); /* Clear out mask */ + sigsuspend(&nomsk); /* Block until something goes off */ +} + +/* DP_SLEEP - Sleep for N seconds. Copied from OSDSUP's os_sleep(), +** see that for more comments. +*/ +void +dp_sleep(int secs) +{ +#if CENV_SYS_DECOSF + sleep(secs); /* Independent of interval timers! */ + +#elif CENV_SYSF_BSDTIMEVAL && CENV_SYSF_SIGSET + /* Must save & restore ITIMER_REAL & SIGALRM, which conflict w/sleep() */ + struct itimerval ztm, otm; + struct sigaction act, oact; + + timerclear(&ztm.it_interval); + timerclear(&ztm.it_value); + setitimer(ITIMER_REAL, &ztm, &otm); + + act.sa_handler = SIG_IGN; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + sigaction(SIGALRM, &act, &oact); + + sleep(secs); /* Do the gubbish */ + + /* Now restore the world */ + sigaction(SIGALRM, &oact, (struct sigaction *)NULL); + setitimer(ITIMER_REAL, &otm, (struct itimerval *)NULL); + +#else +# error "Need implementation for dp_sleep()" +#endif +} + +#endif /* KLH10_DEV_DP */ diff --git a/src/dpsup.h b/src/dpsup.h new file mode 100644 index 0000000..b9e3874 --- /dev/null +++ b/src/dpsup.h @@ -0,0 +1,301 @@ +/* DPSUP.H - Device sub-Process Support definitions (OSD) for KLH10 +*/ +/* $Id: dpsup.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1994, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dpsup.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* + This file defines the IPC mechanism used to communicate between +virtual device code in the KLH10 and the various sub-processes that +may be needed to carry out their operations. For our purposes, "input" +and "output" are defined relative to the virtual PDP10; input is data +coming from a device to the 10, and output is generated by the 10 for +transfer to a device. + + Note that the sub-processes may, in various configurations, be +any of: + +(1) Full subprocess (forked child) + Possible on most process-oriented platforms (Unix). + One process for output; another process for input if + asynch input is possible for device. + Signalling DP done with either user-defined OS signal + or OS semaphore. + Signalling 10 done with user-defined OS signal (10 cannot + waste time polling semaphores). + DP reset done with OS signal and/or process kill. + +(2) Threaded process within KLH10 + Ideal but only possible if platform has reliable threads support. + One thread for output; another for input if asynch input is + possible for device. + Signalling DP done by condition vars (fast semaphores). + OS semaphores also possible though slower. + Signalling 10 done with direct lock & intf setting. + OS signals also possible but slower. + DP reset done with condvar setting and/or thread kill. + +(3) Asynchronous (non-blocking interrupt-driven) within KLH10 + Not always possible, even on Unix (disk in particular). + No distinct DP process/thread, so no DP/10 comm needed. + I/O never blocks; OS signals given when input avail or + output ready. Signal handler can either perform I/O + or set flag for main loop. + DP reset done directly. + +(4) Synchronous within KLH10 + Maximally portable, but slowest mechanism. + No distinct DP process/thread, so no DP/10 comm needed. + Output always blocks; input is polled. + DP reset done directly. + +Only one mechanism (DP_XT_MSIG) is actually implemented at present. + +*/ + +/* + The DP IPC mechanism implemented here has some remote +similarities to the 10-11 communication protocol. Two identical +comm/transfer regions are used, one for each direction to and from the +subprocess. Messages consist of simple commands that may or may not +point to large quantities of information elsewhere. + +To send a message: + Sender waits until Ready flag is clear. + Sender sets up all necessary data for the message, then sets + Ready flag to -1 and signals receiver. + If result is important, can wait until Ready is clear, then + examine Result value. + +To receive a message: + Receiver waits until signaled and Ready flag is set. + Receiver carries out operation specified by data, then sets + Result value and clears Ready. + Receiver signals sender. + + +Should regions be arranged so each is R/W for sender, and RO for receiver? +Can distribute variables appropriately. Prevents wild subproc from +messing up... a little. + +For now, keep everything in one mutually R/W region for simplicity. + +*/ + +#ifndef DPSUP_INCLUDED +#define DPSUP_INCLUDED 1 + +#ifdef RCSID + RCSID(dpsup_h,"$Id: dpsup.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +#ifndef OSDSUP_INCLUDED +# include "osdsup.h" /* For osintf_t etc */ +#endif + +#define DP_XT_MSIG 1 /* Shared mem, use signal for doorbell */ +#define DP_XT_MSEM 2 /* Shared mem, use semaphore for doorbell */ +#define DP_XT_THCV 3 /* Same mem, use thread condition var */ + +#if 0 +union dpcxmech { + struct dpc_xt_msig { + ospid_t dpxt_pid; /* S: Owner/Sender */ + osshm_t dpxt_mem; /* S: Shared mem identifier */ + oscad_t dpxt_sadr; /* S: Loc in sender address space */ + ossig_t dpxt_sig; /* Signal # to use for doorbell */ + } msig; +}; +#endif + +/* DP one-way transfer region +** Ready flag is set to -1 when sender has deposited a message for +** the reader. It is cleared to 0 when receiver has +** processed the message. +** +*/ + /* C=Creator, S=Sender, R=Receiver */ +struct dpx_s { + int dpx_type; /* C: Type of comm mechanism */ +#if 0 + union dpx_osd; /* S/R: OSD stuff for comm mech */ +#else + int dpx_waktyp; /* R: How to wake up rcpt */ + int dpx_wakflg; /* R/S: R sets 1 when trying to do wake */ + int dpx_waksig; /* C: Signal # to use */ + sigset_t dpx_wakmsk; /* C: Mask for signal # */ + int dpx_wakpid; + unsigned char *dpx_rbuf; /* R: R's ptr into buffer */ + + int dpx_dontyp; /* S: How to Ack sender */ + int dpx_donflg; /* S/R: S sets 1 when trying to do ack */ + int dpx_donsig; /* C: Signal # to use */ + sigset_t dpx_donmsk; /* C: Mask for signal # */ + int dpx_donpid; + unsigned char *dpx_sbuf; /* S: S's ptr into same buffer */ +#endif + + size_t dpx_len; /* C: Buffer length */ + size_t dpx_off; /* C: Buffer offset from beg of segment */ + volatile + osintf_t dpx_rdyf; /* S/R: Ready flag */ + int dpx_res; /* R: Result value */ + int dpx_cmd; /* S: Command */ + int dpx_cnt; /* S: Count of data bytes */ + + /* Args of various types -- later could be union */ + int dpx_int; + long dpx_long; + unsigned char *dpx_ucp; +#if 0 + paddr_t dpx_pa; + w10_t dpx_w; + dw10_t dpx_dw; +#endif +}; +typedef struct dpx_s dpx_t; + +/* DP common area, shared between Main (superior) and DP (subproc) +** +*/ +struct dpc_s { + char dpc_magic[4]; /* M: Magic chars saying this is DPC mem seg */ + int dpc_fmtver; /* M: Format version */ + char dpc_id[8]; /* M: App identifier chars */ + int dpc_flags; /* M/DP: Flags */ + int dpc_debug; /* M: Debug flag (separate for efficiency) */ + struct dpx_s dpc_todp; /* M: To subproc from KLH10 */ + struct dpx_s dpc_frdp; /* DP: From subproc to KLH10 */ + + /* Various other DPC info */ + + /* Device-dependent stuff, extensible to arbitrary size */ +}; + +/* Defs for DPC contents */ + +#define DPC_MAGIC "DPM" + +#define DPC_VERSION(maj,min,pat) (((maj)<<10) | ((min)<<5) | (pat)) +#define DPC_GV_MAJ(a) (((a)>>10)&037) +#define DPC_GV_MIN(a) (((a)>>5)&037) +#define DPC_GV_PAT(a) (((a)>>0)&037) + +#define DPSUP_VERSION DPC_VERSION(1,2,0) /* This version of DPSUP */ + +#define DPCF_MEMLOCK 0x1 /* M wants DP to lock its mem if possible */ + + +/* DP handle - private memory */ +struct dp_s { + int dp_type; + long dp_shmid; /* Change to osmid_t later */ + struct dpc_s *dp_adr; + int dp_chpid; /* Change to ospid_t */ +}; +typedef struct dp_s dp_t; + +/* General Facilities */ + +/* Called from superior (KLH10) */ + +int dp_init (dp_t *dp, size_t, int, int, size_t in, + int, int, size_t out); +int dp_start(dp_t *dp, char *pgm); +int dp_stop (dp_t *dp, int timeout); +int dp_reset(dp_t *dp); /* What would this do? */ +int dp_term (dp_t *dp, int timeout); +void dp_exit(dp_t *dp, int res); + + +/* Called from subprocess (dp) */ + +int dp_main (dp_t *dp, int argc, char **argv); + + +/* Called from both for communications */ + +/* struct dpx_s *dp_dpxto(dp_t *); */ /* "To DP" direction */ +/* struct dpx_s *dp_dpxfr(dp_t *); */ /* "From DP" direction */ +#define dp_dpxto(dp) (&((dp)->dp_adr->dpc_todp)) +#define dp_dpxfr(dp) (&((dp)->dp_adr->dpc_frdp)) + +int dp_xstest (dpx_t *); /* TRUE if can send */ +void dp_xsblock(dpx_t *); /* Block for later testing */ +int dp_xswait (dpx_t *); /* Wait until can send */ +unsigned char * + dp_xsbuff(dpx_t *, size_t *); /* Get buffer for send data */ +void dp_xswake(dpx_t *); /* Send; say message ready */ +void dp_xsend(dpx_t *, int, int); /* Send cmd & data */ + +int dp_xrtest (dpx_t *); /* TRUE if can receive (have input) */ +void dp_xrblock(dpx_t *); /* Block for later testing */ +int dp_xrwait (dpx_t *); /* Wait until can rcv (have input) */ +unsigned char * + dp_xrbuff(dpx_t *, size_t *); /* Get pointer to data */ +void dp_xrdone(dpx_t *); /* Receive; say message done */ +void dp_xrdoack(dpx_t *, int); /* Receive; say done, w/res */ +int dp_xrcmd(dpx_t *); /* Get command for msg */ +int dp_xrcnt(dpx_t *); /* Get cnt for msg */ + +/* Misc internals */ +void dp_sigwait(void); /* Wait until any sig happens */ +void dp_sleep(int); /* Sleep for N seconds */ + +/* Facilities for DPCXT_MSIG */ + +#define dp_xtmsig_stest(dpx) ((dpx)->dpx_rdyf == 0) /* TRUE if can send */ +#define dp_xtmsig_swake(dpx) (((dpx)->dpx_rdyf = 1), \ + ((dpx)->dpx_wakflg = 1), \ + kill((dpx)->dpx_wakpid, (dpx)->dpx_waksig)) +#define dp_xtmsig_sblock(dpx) dp_sigwait() +#define dp_xtmsig_swait(dpx) \ + while (!dp_xtmsig_stest(dpx)) dp_xtmsig_sblock(dpx) +#define dp_xtmsig_sbuff(dpx, asiz) \ + ((asiz ? (*(asiz) = (dpx)->dpx_len) : 0), (dpx)->dpx_sbuf) +#define dp_xtmsig_send(dpx, cmd, cnt) \ + ((dpx)->dpx_cmd = (cmd), (dpx)->dpx_cnt = (cnt), dp_xtmsig_swake(dpx)) + +#define dp_xtmsig_rtest(dpx) ((dpx)->dpx_rdyf != 0) /* TRUE if can recv */ +#define dp_xtmsig_rdone(dpx) (((dpx)->dpx_rdyf = 0), \ + ((dpx)->dpx_donflg = 1), \ + kill((dpx)->dpx_donpid, (dpx)->dpx_donsig)) +#define dp_xtmsig_rdoack(dpx, res) ((dpx)->dpx_res = (res), \ + dp_xtmsig_rdone(dpx)) +#define dp_xtmsig_rblock(dpx) dp_sigwait() +#define dp_xtmsig_rwait(dpx) \ + while (!dp_xtmsig_rtest(dpx)) dp_xtmsig_rblock(dpx) +#define dp_xtmsig_rbuff(dpx, asiz) \ + ((asiz ? (*(asiz) = (dpx)->dpx_len) : 0), (dpx)->dpx_rbuf) +#define dp_xtmsig_rcmd(dpx) ((dpx)->dpx_cmd) +#define dp_xtmsig_rcnt(dpx) ((dpx)->dpx_cnt) + +#if 0 +/* For device to register its dp with 10 via device vector */ +int dpcxt_msig_register(struct dpc_s *dpc, struct device *d); +#endif + +/* Misc auxiliary unrelated to DP support but one that exists and + which all DP procs need anyway (to avoid needing full OSDSUP module) +*/ +extern char *dp_strerror(int); + +#endif /* ifndef DPSUP_INCLUDED */ diff --git a/src/dptm03.c b/src/dptm03.c new file mode 100644 index 0000000..ec10bd9 --- /dev/null +++ b/src/dptm03.c @@ -0,0 +1,1631 @@ +/* DPTM03.C - Device sub-Process for KLH10 TM02/3 Magtape Interface +*/ +/* $Id: dptm03.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1994, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dptm03.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* + + This subprocess is intended to handle either actual hardware +tape drives, or virtual tape files. The behavior of each is somewhat +different. The following are the four basic states: + +SOFT-OFFLINE: (UNMOUNTED) + Emulation pretends that a virtual drive exists, without + a tape in it. + The only way to change this state is via a manual MOUNT request that + selects either a virtual tape or a hardware drive. + +SOFT-ONLINE: + A virtual tape spec is online. + A manual {UN}MOUNT request can change to any of the other three states. + In addition, the DPTM_UNL (unload) subproc command will change + this state to the UNMOUNT state. + +HARD-OFFLINE: + A hardware drive spec is mounted but offline. The process is + blocked in an open() waiting for the drive to come back online. + A manual {UN}MOUNT request can change to any of the other three states. + In addition, if the drive comes online the state will change to + HARD-ONLINE and the 10 will be signalled. + +HARD-ONLINE: + A hardware drive spec is mounted and a tape is online. + Emulation attempts to map the hardware state + into the 10 and vice versa as accurately as possible. + A manual {UN}MOUNT request can change to any of the other three states. + In addition, if the DPTM_UNL (unload) subproc command succeeds + in taking the physical drive offline, the state will change to + HARD-OFFLINE. + + + +MOUNT/UNMOUNT requests: + + For the time being these must come from the KLH10 process itself +via the DPTM_MOUNT and DPTM_ROMNT commands. + Eventually it should be possible for such requests to be delivered +asynchronously from outside the KLH10. There are various possible ways to +do this; the main problem is how to agree on a reasonable rendezvous point +beforehand. This could be a socket (port number), IPC message queue (key), +shared memory seg (key), file (pathname), whatever. One appealing thing +about the file approach is that filenames can be much more descriptive; +IPC keys for example are only a single integer value and one can't easily +map from a device name to key, thus requiring another round of mapping. +A simple file-oriented mechanism could be: + + Assume environment variable KLH10_HOME defines home directory of + the running KLH10 of interest (a system might have more than one). + In that directory: + + dv-.dev - Lock file for device + If succeed in locking file, process reads it to obtain + running device's PID and signal # to use (and whatever else). + dv-.req - When signalled, DP proc looks for this filename. + If found, reads it and handles request, flushing file. + Could return result, if one, in: + dv-.sta + +Main problem, as usual, is how to clean up; what happens if DP dies without +a chance to flush the lock file? Any way for a mounter program to verify +that the info corresponds to a live running KLH10, and thus avoid sending +spurious signals to an innocent bystander process? Unix is extremely +deficient in ways for a process to inquire about other processes. + + +MOUNT PROBLEM: + - How can DP detect when hard-mounted drive comes online? + If UNLOAD is given, seems to be no way to get MOL again + other than by periodic checks. + +Is there any chance that SIGIO would work here? + No -- it doesn't. Worse yet, status query doesn't work + either! Once it goes offline, putting in a new tape + doesn't turn status bits online again! Have to close + the FD and open again. + +Open attempt will *hang* until drive is online... + perhaps that's how to notice it? Could have SIG interrupt + out of OPEN attempt, so can still handle requests. + +Note: if non-blocking (O_NONBLOCK) open is done, will succeed even if + drive is offline, but status queries WILL NOT SHOW when it becomes + online again! Just as for post-UNLOAD state. + + +ADDITIONAL NOTES: + + If try to open drive while it's turned off, get I/O error (I think). + + If try to open while it's on but offline, block and wait (good). +[dptm03: Mounted tape device "/dev/rmt0a"] + + When put tape in drive, get: +[dptm03: Tape came online: "/dev/rmt0a"] + + But there's some weird state as follows: + Read tape, then unload tape. + Nothing happens according to dptm03 stderr. + Put tape back in drive. suddenly get: + +[dptm03: Cannot open device "/dev/rmt0a":I/O error] +[dptm03: Closing "/dev/rmt0a"] + + WTF?!?!?! Turns out this was probably the 45-second timeout... + +Prog state \ DevOff Offline Online Unload +------------------- +open non-blocking err:5 OK OK ? +open blocking err:5 blk(*) OK ? +open, doing MT op ? err:5 OK + + blk(*) = blocks for 45 seconds, then returns err:5 (I/O Error) + Otherwise returns as soon as drive comes online. + +Note: returns err:16 (Device Busy) if someone else has drive open (including + self on a different FD) + +[3/10/97] NOTE: as of OSF/1 V4.0 it appears that at least one tape +driver for 1/2" magtape reels has been munged so that attempting to +open the drive for read/write access will *FAIL* with err:13 +"Permission denied" if the tape is loaded without a write-ring -- +i.e. it is write-locked. The drive must be opened with a mode of +O_READ rather than O_RDWR! This does not happen for other drives such +as the 4mm TLZ06. What the hell were they smoking when they +implemented *this*???? + +So, algorithm (on DECOSF anyway) should be: + + [0] SOFTOFF STATE. Attempt mount by opening in R/O non-block mode. + If success, close FD and go to HARDOFF state (next step). + If fail, error will be: + ENOENT: No such file or device + EIO: I/O Error - Drive powered off + EBUSY: Device Busy - someone else has drive open + For all of them, give up and revert to SOFTOFF state. + + [1] HARDOFF state. Re-try open in R/W blocking mode. This will block + for up to 45 seconds. + If success, go to HARDON state (next step). + If fail, error should be: + EIO: I/O Error - open timed out. (this is also + returned if drive is powered off, but we know + from previous step that it exists). + Just try again. If "too many" EIO failures + (say 10 mins), go back to step [0]; + drive may have been powered off. + EACCES: Permission denied - if in R/W mode, *may* mean + that tape is write-locked, so drive must be + re-opened read-only. + Try again in R/O mode. If that succeeds, + verify tape is write-locked, and go back to step [0] + if inconsistent. + If any other failure, go back to step [0]. + + [2] HARDON STATE. When blocking open succeeds, drive should be + online (medium online). + Tell 10 it's online and accept commands for drive. + + * Drive state can change for one of these reasons: + - Command given to UNLOAD tape. The IOCTL does not appear + to return until the command is complete, so when it's + done the state immediately changes to HARDOFF. + - Mount/Unmount request received. State changes to SOFTOFF + and then to whatever results from the mount attempt, + if one. + - Physically taken offline (tape out or powered off). + Any tape operation will return EIO (I/O Error) and + tape status will show MOL off. State changes to + HARDOFF. + - Actual media I/O errors. Tape operation will probably + return EIO just as for going offline, but hopefully + the tape status will continue to show MOL on; state + should remain HARDON. +*/ + +#include +#include +#include +#include +#include + +#include "klh10.h" /* For config params */ + +#if CENV_SYS_UNIX +# include +# include +# include +# include +# include +# include +# include +# include +# include +#endif +#if CENV_SYS_DECOSF +# include /* To support DEVIOCGET */ +#endif + +#include "dpsup.h" /* General DP defs */ +#include "dptm03.h" /* TM03 specific defs */ +#include "vmtape.h" /* Virtual tape hackery */ + +#ifdef RCSID + RCSID(dptm03_c,"$Id: dptm03.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Flag args to os_mtopen() */ +#define OS_MTOF_READONLY 01 /* Open tape drive read-only */ +#define OS_MTOF_NONBLOCK 02 /* Open in non-blocking mode */ + +struct devmt { + struct dp_s d_dp; /* Point to shared memory area */ + struct dptm03_s *d_tm; /* Shorthand ptr to DPTM03 struct in area */ + int d_mntreq; /* TRUE if mount request pending */ + int d_state; +#define DPTM03_STF_HARD 01 /* 0=virtual, 1=hard */ +#define DPTM03_STF_MOL 02 /* 0=offline, 1=online */ +#define DPTM03_STA_SOFTOFF 0 +#define DPTM03_STA_SOFTON (DPTM03_STF_MOL) +#define DPTM03_STA_HARDOFF (DPTM03_STF_HARD) +#define DPTM03_STA_HARDON (DPTM03_STF_HARD|DPTM03_STF_MOL) + int d_openretry; /* Retry count for HARDOFF blocking opens */ + + int d_istape; /* Tape type, MTYP_xxx */ + char *d_path; /* M Tape drive path spec */ +#if CENV_SYS_UNIX + int d_fd; +#endif + size_t d_recsiz; /* Block (record) size to use */ + unsigned char *d_buff; /* Ptr to buffer loc */ + size_t d_blen; /* Actual buffer length */ + int mta_bot, + mta_eot, + mta_eof, + mta_mol, + mta_wrl, + mta_rew, + mta_err; + long mta_frms; + + struct vmtape d_vmt; /* Virtual magtape info */ +} devmt; + + +int os_mtunload(struct devmt *d); +int os_mtrewind(struct devmt *d); +int os_mtspace(struct devmt *d, long unsigned int cnt, int revf); +int os_mtfspace(struct devmt *d, long unsigned int cnt, int revf); +int os_mtopen(struct devmt *d, char *path, int mtof); +int os_mtread(struct devmt *dp); +int os_mtwrite(struct devmt *d, unsigned char *buff, size_t len); +int os_mtclrerr(struct devmt *d); +void os_mtshow(struct devmt *d, FILE *f); +int os_mtstate(struct devmt *d); +int os_mtclose(struct devmt *d); +int os_mtweof(struct devmt *d); +void os_mtmoveinit(struct devmt *d); +int osux_mtop(struct devmt *d, int op, int cnt, char *name); + + +void tmtoten(struct devmt *); +void tentotm(struct devmt *); + +int dptmmount(struct devmt *, char *, char *); +void dptmclear(struct devmt *); +void dptmstat(struct devmt *); + +int devmount(struct devmt *, struct vmtattrs *); +int devclose(struct devmt *); +int devread(struct devmt *d); +int devwrite(struct devmt *d, unsigned char *buff, size_t len); +int devweof(struct devmt *); +int devmolwait(struct devmt *d); +int devunload(struct devmt *d); +int devrewind(struct devmt *d); +int devrspace(struct devmt *d, long unsigned int cnt, int revf); +int devfspace(struct devmt *d, long unsigned int cnt, int revf); + +void sscattn(struct devmt *d); +void chkmntreq(struct devmt *d); + + + +/* For now, include VMT source directly, so as to avoid compile-time +** switch conflicts (eg with KLH10). +*/ +#define os_strerror dp_strerror +#include "vmtape.c" + +#define DBGFLG (devmt.d_tm->dptm_dpc.dpc_debug) + + +/* Low-level support */ + +static void efatal(int num, char *fmt, ...) +{ + fprintf(stderr, "\n%s: ", "dptm03"); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + putc('\n', stderr); + + dp_exit(&devmt.d_dp, num); +} + +static void esfatal(int num, char *fmt, ...) +{ + fprintf(stderr, "\n%s: ", "dptm03"); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fprintf(stderr, " - %s\n", dp_strerror(errno)); + + dp_exit(&devmt.d_dp, num); +} + +static void error(char *fmt, ...) +{ + fprintf(stderr, "\n%s: ", "dptm03"); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } +} + +static void syserr(int num, char *fmt, ...) +{ + fprintf(stderr, "\n%s: ", "dptm03"); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fprintf(stderr, " - %s\n", dp_strerror(num)); +} + +int +main(int argc, char **argv) +{ + register struct devmt *d = &devmt; + + /* General initialization */ + if (!dp_main(&d->d_dp, argc, argv)) { + efatal(1, "DP init failed!"); + } + d->d_tm = (struct dptm03_s *)d->d_dp.dp_adr; /* Make refs easier */ + + /* From here on can refer to shared structure */ + if (DBGFLG) + fprintf(stderr, "[dptm03: Started]"); + + /* Find location and size of record buffer to use */ + d->d_buff = dp_xrbuff(dp_dpxto(&d->d_dp), &d->d_blen); + + /* Set up necessary event handlers for 10-DP communication. + ** For now this is done by DPSUP. + */ + + /* Ignore TTY cruft so CTY hacking in 10 doesn't bother us */ + signal(SIGINT, SIG_IGN); /* Ignore TTY cruft */ + signal(SIGQUIT, SIG_IGN); + + /* Open tape drive initially specified, if one; initialize stuff */ + d->d_state = DPTM03_STA_SOFTOFF; + d->d_istape = MTYP_NONE; + vmt_init(&d->d_vmt, "DPTM03"); + + tentotm(d); /* Start normal command/response process */ + return 1; /* Never returns, but silence compiler */ +} + +/* TMTOTEN - Process to handle unexpected events and report them to the 10. +** Does nothing for now; later could be responsible for listening +** and responding to operations from a user tape-interface process. +*/ + +void tmtoten(register struct devmt *d) +{ + register struct dpx_s *dpx; + register unsigned char *buff; + size_t max; +#if 0 + register int cnt; + int stoploop = 50; +#endif + + dpx = dp_dpxfr(&d->d_dp); /* Get ptr to from-DP comm rgn */ + buff = dp_xsbuff(dpx, &max); /* Set up buffer ptr & max count */ + +#if 0 + for (;;) { + + /* Make sure that buffer is free before clobbering it */ + dp_xswait(dpx); /* Wait until buff free */ + + /* OK, now do a blocking read on external command input! */ + if ((cnt = read(pffd, buff, max)) <= MINREQPKT) { + + if (cnt < 0 && (errno == EINTR)) /* Ignore spurious signals */ + continue; + + /* Error of some kind */ + fprintf(stderr, "dptm03: REQPKT read = %d, ", cnt); + if (cnt < 0) { + if (--stoploop <= 0) + efatal(1, "Too many retries, aborting"); + fprintf(stderr, "errno %d = %s\r\n", + errno, dp_strerror(errno)); + } else if (cnt > 0) + fprintf(stderr, "no REQPKT data\r\n"); + else fprintf(stderr, "no REQPKT\r\n"); + + continue; /* For now... */ + } + + /* Normal packet, pass to 10 via DPC */ + /* Or simply process here, then hand up results */ + dp_xsend(dpx, DPTM_RPKT, cnt); + } +#endif /* 0 */ +} + +/* Slave Status Change - Signal attention. +** Drive came online as a result of either: +** (1) a successful soft mount request +** (2) a hard mount came online +*/ +void sscattn(register struct devmt *d) +{ + register struct dpx_s *dpx; + + dpx = dp_dpxfr(&d->d_dp); /* Get ptr to from-DP comm rgn */ + + dp_xswait(dpx); /* Wait until receiver ready */ + dp_xsend(dpx, DPTM_MOUNT, 0); /* Send note to 10! */ + +} + +void chkmntreq(register struct devmt *d) +{ + d->d_mntreq = FALSE; /* For now */ +} + +/* TENTOTM - Main loop for thread handling commands from the 10. +** Reads DPC message from 10 and interprets it, returning a +** result code (and/or data). +*/ + +void tentotm(register struct devmt *d) +{ + register struct dpx_s *dpx; + register unsigned char *buff; + size_t max; + register int rcnt; + int res; + int cmd; + + + if (DBGFLG) + fprintf(stderr, "[dptm03: in tentotm]"); + + dpx = dp_dpxto(&(d->d_dp)); /* Get ptr to "To-DP" xfer stuff */ + buff = dp_xrbuff(dpx, &max); + + for (;;) { + + /* Wait until 10 has a command for us */ + while (!dp_xrtest(dpx)) { + if (d->d_mntreq) + chkmntreq(d); /* Check out possible mount req */ + else if (d->d_state == DPTM03_STA_HARDOFF) + devmolwait(d); /* Hard offline, wait for change */ + else + dp_xrblock(dpx); /* Block until something happens */ + } + + /* Reset some stuff for every command */ + res = DPTM_RES_SUCC; /* Default is successful op */ + d->d_tm->dptm_col = FALSE; + d->d_tm->dptm_err = 0; + + /* Process command from 10! */ + switch (cmd = dp_xrcmd(dpx)) { + + default: + fprintf(stderr, "[dptm03: Unknown cmd %o]\r\n", dp_xrcmd(dpx)); + res = DPTM_RES_FAIL; + break; + + case DPTM_RESET: /* Reset DP */ + /* Attempt to do complete reset */ + fprintf(stderr, "[dptm03: Reset request]\r\n"); +#if 0 + dptm_restart(2); +#endif + break; + + case DPTM_MOUNT: /* Mount tape specified by path/arg strings */ + if (!dptmmount(d, + (d->d_tm->dptm_pathx + ? (char *)&buff[d->d_tm->dptm_pathx] : NULL), + (d->d_tm->dptm_argsx + ? (char *)&buff[d->d_tm->dptm_argsx] : NULL))) { + res = DPTM_RES_FAIL; + } + break; + + case DPTM_NOP: /* No operation */ + break; + + case DPTM_UNL: /* Unload (implies rewind & unmount) */ + if (!devunload(d)) { /* Unload/Unmount tape */ + res = DPTM_RES_FAIL; + } + break; + + case DPTM_REW: /* Rewind tape */ + if (!devrewind(d)) { /* Rewind tape */ + res = DPTM_RES_FAIL; + } + break; + + case DPTM_WTM: /* Write Tape Mark (EOF) */ + if (!devweof(d)) { /* Write EOF */ + res = DPTM_RES_FAIL; + } + break; + + case DPTM_SPF: /* Space N records forward */ + case DPTM_SPR: /* Space N records reverse */ + rcnt = dp_xrcnt(dpx); /* Get count */ + if (!devrspace(d, + (unsigned long)rcnt, + (cmd==DPTM_SPR ? 1 : 0))) + res = DPTM_RES_FAIL; + break; + + case DPTM_SFF: /* Space N files forward */ + case DPTM_SFR: /* Space N files reverse */ + rcnt = dp_xrcnt(dpx); /* Get count */ + if (!devfspace(d, + (unsigned long)rcnt, + (cmd==DPTM_SFR ? 1 : 0))) + res = DPTM_RES_FAIL; + break; + + case DPTM_ER3: /* Erase 3 inches */ + /* Ignored for now, but later may be able to support */ + break; + + case DPTM_WRT: /* Write record of N bytes */ + rcnt = dp_xrcnt(dpx); /* Get length to write */ + if (!devwrite(d, buff, rcnt)) { + /* Handle write error of some kind */ + res = DPTM_RES_FAIL; + d->d_tm->dptm_err = 1; + } + break; + + case DPTM_RDF: /* Read forward up to N bytes */ + if (DBGFLG) + fprintf(stderr, "[dptm03: Read]\r\n"); +#if 0 + rcnt = dpx->dpx_len; /* Get max len can read */ +#endif + if (!devread(d)) { + /* Handle read error of some kind */ + /* Could be EOF, EOT, or error; let dptmstat handle it */ + } + break; + + case DPTM_RDR: /* Read reverse up to N bytes */ + /* No Unix system provides Read-reverse to the users. What + ** this code does is simulate it by backspacing over one + ** record and reading it in. Note the backspace operation + ** succeeds even if BOT or EOF is encountered instead of + ** a record, so we check the frame count to make sure it + ** went over a record. Operation as a whole always succeeds + ** unless there is some gross error. + ** Note special hack to return correct frame count. + */ + if (DBGFLG) + fprintf(stderr, "[dptm03: Read-reverse]\r\n"); + if (!devrspace(d, (long)1, 1)) { + res = DPTM_RES_FAIL; + d->d_tm->dptm_err = 1; + break; + } + dptmstat(d); /* Update vars, to check frms */ + if (d->d_tm->dptm_frms) { + /* Space succeeded, so read record in */ + long rrfrms; + devread(d); /* Read the record */ + dptmstat(d); /* Update vars to get data frames */ + rrfrms = d->d_tm->dptm_frms; /* Remember them */ + devrspace(d, (long)1, 1); /* Back up 1 record again */ + dptmstat(d); /* Update vars one last time */ + + /* Ugly crock - in order to set frame count explicitly after + dptmstat() is done, we have to skip the normal + end-of-command processing (or rather, replicate it here). + */ + d->d_tm->dptm_frms = rrfrms; /* Restore data frame cnt */ + dp_xrdoack(dpx, res); /* ACK the command */ + continue; /* and resume loop */ + } + break; + + case DPTM_SNS: /* Sense? */ + fprintf(stderr, "[dptm03: Sense not supported]\r\n"); + res = DPTM_RES_FAIL; + break; + + } + dptmstat(d); /* Update most status vars */ + + /* Command done, return result and tell 10 we're done */ + dp_xrdoack(dpx, res); + } +} + +int +dptmmount(register struct devmt *d, + char *path, + char *args) +{ + struct vmtattrs v; + + if (DBGFLG) + fprintf(stderr, "[dptm03: Mount request \"%s\" \"%s\"]\r\n", + (path ? path : ""), + (args ? args : "")); + + devclose(d); /* Force unmount of any existing tape */ + dptmclear(d); + d->d_tm->dptm_type = d->d_istape; /* Update tape type (shd be NONE) */ + + if (!path) /* Just wanted unmount, that's all! */ + return TRUE; + + /* Set up initial path */ + if (strlen(path) >= sizeof(v.vmta_path)) { + fprintf(stderr, "[dptm03: mount path too long (%d max)]\r\n", + (int)sizeof(v.vmta_path)); + return FALSE; + } + strcpy(v.vmta_path, path); /* Set path */ + v.vmta_mask = VMTA_PATH; /* Initialize attribs */ + + /* Parse args if any, adding to attribs */ + if (args && !vmt_attrparse(&d->d_vmt, &v, args)) { + fprintf(stderr, "[dptm03: Mount failed, bad args]\r\n"); + return FALSE; + } + + /* Do it! */ + if (!devmount(d, &v)) + return FALSE; + + d->d_tm->dptm_type = d->d_istape; /* Report back on type */ + + return TRUE; +} + + +/* DPTMSTAT - Set shared status values +*/ +void dptmstat(register struct devmt *d) +{ + register struct dptm03_s *dptm = d->d_tm; + register struct vmtape *t = &d->d_vmt; + + switch (d->d_istape) { + case MTYP_NONE: + dptm->dptm_mol = FALSE; /* Medium online */ + dptm->dptm_wrl = FALSE; /* Write-locked */ + dptm->dptm_bot = FALSE; + dptm->dptm_eot = FALSE; + dptm->dptm_eof = FALSE; + dptm->dptm_frms = 0; + break; + + case MTYP_VIRT: + dptm->dptm_mol = vmt_ismounted(t); /* Medium online */ + dptm->dptm_wrl = !vmt_iswritable(t); /* Write-locked */ + dptm->dptm_bot = vmt_isatbot(t); + dptm->dptm_eot = vmt_isateot(t); + dptm->dptm_eof = vmt_isateof(t); + dptm->dptm_err = vmt_errors(t); + dptm->dptm_frms = vmt_framecnt(t); + break; + + default: + if (dptm->dptm_mol = d->mta_mol) { + if (d->d_state == DPTM03_STA_HARDOFF) { + if (DBGFLG) + fprintf(stderr, "[dptm03: Tape came online: \"%s\"]\r\n", + d->d_path); + dptm->dptm_col = TRUE; /* Special "Came-Online" flg */ + sscattn(d); /* Signal 10 re change */ + + } + d->d_state = DPTM03_STA_HARDON; + } else { + if (d->d_state == DPTM03_STA_HARDON) { + if (DBGFLG) + fprintf(stderr, "[dptm03: Tape went offline: \"%s\"]\r\n", + d->d_path); + } + d->d_state = DPTM03_STA_HARDOFF; + } +#if 1 + /* Check for bogosity */ + if (d->mta_bot && d->mta_eof) { + fprintf(stderr,"[dptm03: dptmstat had both BOT and TM!]\r\n"); + d->mta_eof = FALSE; + } +#endif + dptm->dptm_wrl = d->mta_wrl; + dptm->dptm_bot = d->mta_bot; + dptm->dptm_eot = d->mta_eot; + dptm->dptm_eof = d->mta_eof; + dptm->dptm_err = d->mta_err; + dptm->dptm_frms = d->mta_frms; + break; + } +} + +void dptmclear(register struct devmt *d) +{ + register struct dptm03_s *dptm = d->d_tm; + + dptm->dptm_col = FALSE; + dptm->dptm_mol = FALSE; + dptm->dptm_wrl = FALSE; + dptm->dptm_pip = FALSE; + + dptm->dptm_bot = FALSE; + dptm->dptm_eot = FALSE; + dptm->dptm_eof = FALSE; + dptm->dptm_frms = 0; + dptm->dptm_err = 0; +} + + +/* Generic device routines (null, virtual, and real) +** Open, Close, Read, Write, Write-EOF, Write-EOT. +*/ + +/* MOUNT - must already have set up +** d_buff, d_len. +** +** Assumes devclose() has already been applied to unmount existing +** tape if necessary. +*/ +int devmount(register struct devmt *d, + register struct vmtattrs *ta) +{ + int typ; + char *opath = ta->vmta_path; + int wrtf; + char *path = NULL; + + /* Determine basic type type here. For now, anything that + sets vmta_dev is assumed to be a "hard" device. + Nothing yet depends on exactly what kind of device. + */ + if (ta->vmta_mask & VMTA_DEV) { + typ = MTYP_HALF; + wrtf = VMT_MODE_UPDATE; + } else { + typ = MTYP_VIRT; + wrtf = (ta->vmta_mask & VMTA_MODE) + ? ta->vmta_mode : VMT_MODE_RDONLY; + } + + /* Check out path argument and copy it */ + if (typ != MTYP_NONE) { + if (!opath || !*opath) { + fprintf(stderr, "[dptm03: Null mount path]\r\n"); + return FALSE; + } + path = malloc(strlen(opath)+1); + strcpy(path, opath); + } + + /* Open the necessary files */ + switch (typ) { + + case MTYP_NONE: + return TRUE; + + case MTYP_VIRT: + /* Mount & open virtual tape. + */ + if (!vmt_attrmount(&(d->d_vmt), ta)) { + fprintf(stderr, + "[dptm03: Couldn't mount virtual tape for %s: %s]\r\n", + ( (wrtf==VMT_MODE_RDONLY) ? "reading" + : (wrtf==VMT_MODE_CREATE) ? "create" + : (wrtf==VMT_MODE_UPDATE) ? "update" : "unknown-op"), + path); + free(path); + return FALSE; + } + /* Check buffer length */ + if (d->d_vmt.mt_tdr.tdmaxrsiz > DPTM_MAXRECSIZ) { + fprintf(stderr, "[dptm03: Tapedir contains record larger than max (%ld > max %ld)]\r\n", + (long)d->d_vmt.mt_tdr.tdmaxrsiz, (long)DPTM_MAXRECSIZ); + } + d->d_state = DPTM03_STA_SOFTON; + if (DBGFLG) + fprintf(stderr, "[dptm03: Mounted virtual %s tape \"%s\"]\r\n", + (wrtf ? "R/W" : "RO"), path); + + break; + + default: + /* Check out path to see if it's a real tape device or not. + ** Do this by opening in non-blocking mode, so that we don't hang up. + ** NOTE however: Even if it succeeded and the state is "online", + ** it's still closed immediately because non-blocking open also implies + ** non-blocking I/O, which is NOT what we want. + */ + if (!os_mtopen(d, path, OS_MTOF_READONLY|OS_MTOF_NONBLOCK)) { + fprintf(stderr, "[dptm03: Cannot mount device \"%s\": %s]\r\n", + path, dp_strerror(-1)); + free(path); + return FALSE; + } + /* Won, we think it's a tape device. + ** Close it right away and set state to re-try with a blocking open. + */ + os_mtclose(d); + d->d_state = DPTM03_STA_HARDOFF; + d->d_openretry = d->d_tm->dptm_blkopen; + if (DBGFLG) + fprintf(stderr, "[dptm03: Mounted tape device \"%s\"]\r\n", path); + break; + } + + /* Set up VMT vars, with optional tape-info if specified. + ** Note no need to set up virtual tape stuff, vmt_attrmount + ** already did that. + */ + d->d_istape = typ; + d->d_path = path; + + return TRUE; +} + +/* Special frob only called for mounted hard-device tapes +** while waiting for them to go online. State will be DPTM03_STA_HARDOFF. +*/ +int devmolwait(struct devmt *d) +{ + int res; + sigset_t mask, omask; + int mtof = 0; /* Start with R/W open */ + + sigfillset(&mask); + + retry: + sigprocmask(SIG_UNBLOCK, &mask, &omask); /* Allow sigs to break out */ + res = os_mtopen(d, d->d_path, mtof); /* Do blocking open */ + sigprocmask(SIG_SETMASK, &omask, (sigset_t *)NULL); /* Restore mask */ + + if (res && d->mta_mol) { + dptmstat(d); /* Won, set external state! */ + + } else if (!res && d->mta_err) { + /* If failed, check out error. On AXP OSF, block times out after + ** a mere 45 seconds! + */ + if (d->mta_err == EIO) { /* Probably timed out? */ + recheck: + if (--(d->d_openretry) > 0) /* yeah, bump retry count */ + return FALSE; /* Still OK to stay blocked */ + + /* Retry count expired; attempt a non-blocking open to verify + ** that drive still exists and is powered on. + */ + if (!os_mtopen(d, d->d_path, OS_MTOF_READONLY|OS_MTOF_NONBLOCK)) { + fprintf(stderr, "[dptm03: Device \"%s\" no longer openable: %s]\r\n", + d->d_path, dp_strerror(-1)); + devclose(d); + return FALSE; + } + /* Still there, so (after closing FD) reset blocking opens */ + os_mtclose(d); + d->d_openretry = d->d_tm->dptm_blkopen; + if (DBGFLG) + fprintf(stderr, "[dptm03: Device \"%s\" still openable]\r\n", + d->d_path); + return FALSE; + + } else if ((d->mta_err == EACCES) && (mtof == 0)) { + /* Attempting R/W open and got "Permission denied"? + See Note of 3/10/97. This check is needed due to hideous + lossage by OSF/1 V4.0 reel magtape driver. + */ + mtof = OS_MTOF_READONLY; /* Attempt R/O open instead */ + d->mta_err = 0; + if (DBGFLG) + fprintf(stderr, "[dptm03: Device \"%s\" R/W access denied; trying R/O]\r\n", + d->d_path); + goto retry; + } + + /* Some other kind of fatal error, + ** give up and forget about this device; "unmount" it. + */ + fprintf(stderr, "[dptm03: Device \"%s\" cannot be opened: %s]\r\n", + d->d_path, dp_strerror(-1)); + devclose(d); + } + + /* Opened successfully! Do final consistency check to avoid + possible screw case where we blocked attempting to open it read-only + and by the time it succeeded the tape was actually writable. If + this happens, try again using read/write mode. + This has the potential to be an infinite loop if the O/S is really + screwing us over, so at least give caller a chance to do other + stuff by going to "recheck" instead of "retry". + */ + if (mtof && !(d->mta_wrl)) { + /* Opened read-only, but drive isn't write-locked after all!! */ + if (DBGFLG) + fprintf(stderr, "[dptm03: Device \"%s\" opened R/O but not write-locked, retrying]\r\n", + d->d_path); + os_mtclose(d); + goto recheck; + } + + if (DBGFLG) + fprintf(stderr, "[dptm03: Device \"%s\" opened for %s]\r\n", + d->d_path, (mtof ? "R/O" : "R/W")); + return res; +} + + + +int devclose(struct devmt *d) +{ + int res; + + if (DBGFLG && d->d_path) + fprintf(stderr, "[dptm03: Closing \"%s\"]\r\n", d->d_path); + + switch (d->d_istape) { + case MTYP_NONE: + res = TRUE; + break; + case MTYP_VIRT: + res = vmt_unmount(&d->d_vmt); /* Close virtual tape */ + break; + default: + res = os_mtclose(d); /* Close real tape */ + break; + } + + /* Force us to forget about it even if above stuff failed */ + d->d_state = DPTM03_STA_SOFTOFF; + d->d_istape = MTYP_NONE; + if (d->d_path) { + free(d->d_path); + d->d_path = NULL; + } + return res; +} + + +int devunload(struct devmt *d) +{ + if (DBGFLG) + fprintf(stderr, "[dptm03: Unload]\r\n"); + + switch (d->d_istape) { + + case MTYP_NONE: + d->d_vmt.mt_err = TRUE; /* Error, can't do anything */ + return FALSE; + + case MTYP_VIRT: + /* Virtual tape, unmount it (updates vmt params itself) */ + return vmt_unmount(&(d->d_vmt)); + + default: + /* Real tape, attempt physical unload */ + if (!os_mtunload(d)) { + fprintf(stderr, "[dptm03: unload error on %s: %s]\r\n", + d->d_path, dp_strerror(-1)); + return FALSE; + } + os_mtclose(d); /* Close it, take offline */ + d->d_state = DPTM03_STA_HARDOFF; + if (DBGFLG) + fprintf(stderr, "[dptm03: Tape went offline: \"%s\"]\r\n", + d->d_path); + return TRUE; + } +} + +/* Read one data unit (record, tapemark, etc) from device +** Returns 1 if read something +** Returns 0 if read nothing, but no error +** Returns -1 if error of some kind +** For now, note assumes d_buff and d_blen are args. +*/ + +int devread(struct devmt *d) +{ + switch (d->d_istape) { + + case MTYP_NONE: + d->d_vmt.mt_frames = 0; /* No data read */ + d->d_vmt.mt_err = TRUE; /* Error, can't do anything */ + return FALSE; + + case MTYP_VIRT: + /* Virtual tape, read in (updates vmt params itself) */ + return vmt_rget(&(d->d_vmt), d->d_buff, d->d_blen); + + default: + /* Real tape, attempt physical read */ + if (!os_mtread(d)) { /* Attempt read from device */ + fprintf(stderr, "[dptm03: read error on %s: %s]\r\n", + d->d_path, dp_strerror(-1)); + return FALSE; + } + return TRUE; + } +} + +/* Write one record to device. +*/ +int devwrite(register struct devmt *d, unsigned char *buff, size_t len) +{ + if (DBGFLG) + fprintf(stderr, "[dptm03: Write %ld]\r\n", (long)len); + + switch (d->d_istape) { + + case MTYP_NONE: + d->d_vmt.mt_frames = 0; /* No data written */ + d->d_vmt.mt_err = TRUE; /* Error, can't do anything */ + return FALSE; + + case MTYP_VIRT: + /* Virtual tape, write out (updates vmt params itself) */ + return vmt_rput(&(d->d_vmt), buff, len); + + default: + /* Real tape, attempt physical write */ + if (!os_mtwrite(d, buff, len)) { + fprintf(stderr, "[dptm03: write error on %s: %s]\r\n", + d->d_path, dp_strerror(-1)); + return FALSE; + } + return TRUE; + } +} + +int devweof(register struct devmt *d) +{ + switch (d->d_istape) { + + case MTYP_NONE: + d->d_vmt.mt_err = TRUE; /* Error, can't do anything */ + return FALSE; + + case MTYP_VIRT: + /* Virtual tape, write EOF (updates vmt params itself) */ + return vmt_eof(&(d->d_vmt)); + + default: + /* Real tape, attempt physical tapemark write */ + if (!os_mtweof(d)) { + fprintf(stderr, "[dptm03: TM write error on %s: %s]\r\n", + d->d_path, dp_strerror(-1)); + return FALSE; + } + return TRUE; + } +} + +int devrewind(register struct devmt *d) +{ + if (DBGFLG) + fprintf(stderr, "[dptm03: Rewind]\r\n"); + + switch (d->d_istape) { + + case MTYP_NONE: + d->d_vmt.mt_err = TRUE; /* Error, can't do anything */ + return FALSE; + + case MTYP_VIRT: + /* Virtual tape, rewind (updates vmt params itself) */ + return vmt_rewind(&(d->d_vmt)); + + default: + /* Real tape, attempt physical rewind */ + if (!os_mtrewind(d)) { + fprintf(stderr, "[dptm03: rewind error on %s: %s]\r\n", + d->d_path, dp_strerror(-1)); + return FALSE; + } + return TRUE; + } +} + +int devrspace(register struct devmt *d, long unsigned int cnt, int revf) +{ + unsigned long res; + + if (DBGFLG) + fprintf(stderr, "[dptm03: RecSpace %ld%s]\r\n", + cnt, (revf ? " reverse" : "")); + + switch (d->d_istape) { + + case MTYP_NONE: + d->d_vmt.mt_err = TRUE; /* Error, can't do anything */ + return FALSE; + + case MTYP_VIRT: + /* Virtual tape, returns in mt_frames the # records spaced back */ + if (!vmt_rspace(&(d->d_vmt), revf, cnt)) { + d->d_vmt.mt_err = TRUE; + return FALSE; + } + return TRUE; + + default: + /* Real tape, attempt physical reverse space */ + if (!os_mtspace(d, cnt, revf)) { + fprintf(stderr, "[dptm03: rspace error on %s: %s]\r\n", + d->d_path, dp_strerror(-1)); + return FALSE; + } + return TRUE; + } +} + +int devfspace(register struct devmt *d, long unsigned int cnt, int revf) +{ + unsigned long res; + + if (DBGFLG) + fprintf(stderr, "[dptm03: FileSpace %ld%s]\r\n", + cnt, (revf ? " reverse" : "")); + + switch (d->d_istape) { + + case MTYP_NONE: + d->d_vmt.mt_err = TRUE; /* Error, can't do anything */ + return FALSE; + + case MTYP_VIRT: + /* Virtual tape, space forward (updates mt_frames params itself) */ + if (!(res = vmt_fspace(&(d->d_vmt), revf, cnt))) { + d->d_vmt.mt_err = TRUE; + } + return res; + + default: + /* Real tape, attempt physical space */ + if (!os_mtfspace(d, cnt, revf)) { + fprintf(stderr, "[dptm03: fspace error on %s: %s]\r\n", + d->d_path, dp_strerror(-1)); + return FALSE; + } + return TRUE; + } +} + +/* OS Hardware Magtape handling routines +*/ + +/* Initialize flags for tape movement +*/ +void +os_mtmoveinit(register struct devmt *d) +{ + d->mta_bot = d->mta_eot = d->mta_eof = 0; + d->mta_rew = 0; + d->mta_err = 0; + d->mta_frms = 0; +} + +#if CENV_SYS_UNIX +/* General-purpose ioctl operation invocation +*/ +int +osux_mtop(register struct devmt *d, int op, int cnt, char *name) +{ + struct mtop mtcmd; + + mtcmd.mt_op = op; + mtcmd.mt_count = cnt; + if (ioctl(d->d_fd, MTIOCTOP, &mtcmd) < 0) { + if (name) + fprintf(stderr, "[dptm03: %s ioctl failed: %s]\r\n", + name, dp_strerror(-1)); + d->mta_err++; + return FALSE; + } + return TRUE; +} + +int +os_mtstate(register struct devmt *d) +{ + /* Unfortunately there is no general way to support this stuff. */ +#if CENV_SYS_DECOSF + struct devget dg; /* Mostly use stat and category_stat */ + + if (ioctl(d->d_fd, DEVIOCGET, &dg) < 0) + return FALSE; + + d->mta_eof = ((dg.category_stat & DEV_TPMARK) != 0); + d->mta_rew = ((dg.category_stat & DEV_RWDING) != 0); + + d->mta_bot = ((dg.stat & DEV_BOM) != 0); + d->mta_eot = ((dg.stat & DEV_EOM) != 0); + d->mta_mol = ((dg.stat & DEV_OFFLINE) == 0); /* Invert sense */ + d->mta_wrl = ((dg.stat & DEV_WRTLCK) != 0); + d->mta_err = ((dg.stat & (DEV_SOFTERR|DEV_HARDERR)) != 0); + + /* Check for bogosity */ + if (d->mta_bot && d->mta_eof) { + fprintf(stderr,"[dptm03: os_mtstate sez both BOT and TM!]\r\n"); + d->mta_eof = FALSE; + } + + return TRUE; +#elif CENV_SYS_SUN || CENV_SYS_SOLARIS + struct mtget mt; + + if (ioctl(d->d_fd, MTIOCGET, (char *)&mt) < 0) + return FALSE; + /* Do the pitiful best we can */ + d->mta_err = (mt.mt_erreg != 0); + d->mta_bot = ((mt.mt_fileno == 0) && (mt.mt_blkno == 0)); + d->mta_eof = ((mt.mt_fileno != 0) && (mt.mt_blkno == 0)); + + d->mta_rew = 0; /* Pretend never rewinding, barf */ + d->mta_eot = 0; /* Pretend never at EOT, barf */ + d->mta_mol = 1; /* Pretend always online, barf */ + d->mta_wrl = 0; /* Pretend never write-locked, barf */ +# if 0 + d->mta_rew = ((dg.category_stat & DEV_RWDING) != 0); + d->mta_eot = ((dg.stat & DEV_EOM) != 0); + d->mta_mol = ((dg.stat & DEV_OFFLINE) == 0); /* Invert sense */ + d->mta_wrl = ((dg.stat & DEV_WRTLCK) != 0); +# endif + +#else + return FALSE; +#endif +} +#endif /* CENV_SYS_UNIX */ + +int +os_mtopen(struct devmt *d, char *path, int mtof) +{ + + os_mtmoveinit(d); /* Clear movement flags */ + d->mta_mol = 0; /* Plus general state */ + d->mta_wrl = 0; + +#if CENV_SYS_UNIX + { + int fd; + + fd = open(path, + ( ((mtof & OS_MTOF_READONLY) ? O_RDONLY : O_RDWR) + | ((mtof & OS_MTOF_NONBLOCK) ? O_NONBLOCK : 0)), + 0600); + if (fd < 0) { + + /* Check out failures. Only two are allowed. */ + switch (errno) { + case EWOULDBLOCK: + case EINTR: /* EINTR is specifically OK */ + break; + default: + d->mta_err = errno; + break; + } + return FALSE; + } + d->d_fd = fd; + if (!os_mtstate(d)) { /* Try to set up current state */ + d->mta_mol = TRUE; + d->mta_wrl = TRUE; /* If can't, make best guess */ + d->mta_bot = TRUE; + } + } + return TRUE; +#else + return FALSE; +#endif +} + +int +os_mtclose(struct devmt *d) +{ + os_mtmoveinit(d); /* Clear movement flags */ + d->mta_mol = 0; /* Plus general state */ + d->mta_wrl = 0; + +#if CENV_SYS_UNIX + return close(d->d_fd); +#else + return TRUE; +#endif +} + +int +os_mtread(register struct devmt *d) +{ +#if CENV_SYS_UNIX + /* Don't try to support retries here. If OS doesn't do it, there isn't + ** a whole lot we can do better. (But then again, it's Un*x, so...) + */ + unsigned int res; + + os_mtmoveinit(d); /* Clear movement flags */ + + for (;;) + { + switch (res = read(d->d_fd, d->d_buff, d->d_blen)) { + case 0: /* Tapemark */ + if (!os_mtstate(d)) { + d->mta_eof = TRUE; /* Best guess if no OS state */ + } + return TRUE; + + default: /* Assume record read and all's well */ + d->mta_frms = res; + return TRUE; + + case -1: /* Error */ + if (errno == EINTR) + continue; + if (errno == ENOSPC) { /* OSF/1 returns this for EOT */ + if (!os_mtstate(d)) { + d->mta_eot = TRUE; /* Best guess if no OS state */ + } + return TRUE; + } + fprintf(stderr, "[dptm03: Tape read error: %s\r\n", + dp_strerror(-1)); + os_mtshow(d, stderr); /* Show full status */ + (void) os_mtstate(d); /* Attempt to pass on OS state */ + d->mta_err++; /* Always indicate error */ + fprintf(stderr, "]\r\n"); + return FALSE; + } + } +#else /* CENV_SYS_UNIX */ + return FALSE; +#endif +} + +int +os_mtwrite(register struct devmt *d, + register unsigned char *buff, + size_t len) +{ +#if CENV_SYS_UNIX + + unsigned int ret; + + os_mtmoveinit(d); /* Clear movement flags */ + + for (;;) + { + if ((ret = write(d->d_fd, buff, len)) == len) { + d->mta_frms = ret; /* Won! Assume all's well */ + return TRUE; + } + + /* Error of some kind */ + if (ret == -1) { + int saverr = errno; + + if (errno == EINTR) + continue; + fprintf(stderr, "[dptm03: write err: %s]\r\n", dp_strerror(-1)); + os_mtshow(d, stderr); /* Show full status */ + + if (!os_mtstate(d)) { /* Try getting general OS status */ + if (saverr == ENOSPC) { /* Best guess - reached EOT? */ + d->mta_eot = TRUE; + } + } + d->mta_err++; /* Always indicate error */ + return FALSE; + } + + fprintf(stderr, "[dptm03: write trunc: %ld => %d]\r\n", (long)len, ret); + (void) os_mtstate(d); /* Try to get general OS status */ + d->mta_frms = ret; /* Return # frames written */ + return TRUE; /* Some kind of error, but let caller figure out */ + } +#else /* CENV_SYS_UNIX */ + return FALSE; +#endif +} + +int +os_mtweof(struct devmt *d) /* Write a tapemark */ +{ + os_mtmoveinit(d); /* Clear movement flags */ + +#if CENV_SYS_UNIX + if (!osux_mtop(d, MTWEOF, 1, "MTWEOF")) { + d->mta_err++; /* Error of some kind */ + (void) os_mtstate(d); /* Try to recover state from OS */ + return FALSE; + } + /* Assume all's well, no status change */ + return TRUE; +#else + return FALSE; +#endif +} + +int +os_mtunload(struct devmt *d) /* Try to unload tape */ +{ + d->mta_mol = FALSE; /* Say no longer online */ + os_mtmoveinit(d); /* Clear movement flags */ +#if CENV_SYS_UNIX + lseek(d->d_fd, (long)0, 0); /* Barf - See DECOSF "man mtio" */ + + /* Do MTUNLOAD. Known to be defined by DECOSF and LINUX; other + systems must use MTOFFL as closest equivalent. + */ +# ifndef MTUNLOAD +# define MTUNLOAD MTOFFL /* For SUN/SOLARIS/XBSD, closest equiv. */ +# endif + return osux_mtop(d, MTUNLOAD, 1, "MTUNLOAD"); +#else + return FALSE; +#endif +} + +int +os_mtrewind(struct devmt *d) /* Rewind tape */ +{ + os_mtmoveinit(d); /* Clear movement flags */ + d->mta_rew = TRUE; /* Now rewinding */ +#if CENV_SYS_UNIX + lseek(d->d_fd, (long)0, 0); /* Barf - See DECOSF "man mtio" */ + if (!osux_mtop(d, MTREW, 1, "MTREW")) { + if (!os_mtstate(d)) { + d->mta_rew = FALSE; + } + return FALSE; + } + if (!os_mtstate(d)) { /* Won, get general OS status */ + d->mta_rew = FALSE; /* If can't, make best guess */ + d->mta_bot = TRUE; + } + return TRUE; +#else + return FALSE; +#endif +} + + +int /* Space Record */ +os_mtspace(struct devmt *d, long unsigned int cnt, int revf) +{ +#if CENV_SYS_UNIX + int op = (revf ? MTBSR : MTFSR); + + os_mtmoveinit(d); /* Clear movement flags */ + + /* Not clear if it's possible to find out just how many records were + ** spaced over, so do it the hard way for now. + */ + for (; cnt; --cnt) { + if (!osux_mtop(d, op, 1, (char *)NULL)) { + break; + } + d->mta_frms++; /* Else assume all's well, bump counter */ + } + + /* Can't distinguish real error from hitting tapemark, so always + ** pretend we won. + */ + if (!os_mtstate(d)) { /* If fail, try to get OS state */ + d->mta_eof = TRUE; /* Best guess (Note may be BOT/EOT!!) */ + } + return TRUE; + +#else + return FALSE; +#endif +} + +int /* Space File */ +os_mtfspace(struct devmt *d, long unsigned int cnt, int revf) +{ +#if CENV_SYS_UNIX + int op = (revf ? MTBSF : MTFSF); + + os_mtmoveinit(d); /* Clear movement flags */ + + /* Not clear if it's possible to find out just how many files were + ** spaced over, so do it the hard way for now. + */ + for (; cnt; --cnt) { + if (!osux_mtop(d, op, 1, (char *)NULL)) { + if (!os_mtstate(d)) { /* If fail, try to get OS state */ + if (revf) /* If can't, make best guess */ + d->mta_bot = TRUE; + else + d->mta_eot = TRUE; + } + return TRUE; + } + d->mta_frms++; /* Else assume all's well, bump counter */ + } + + /* Need to check general state in case hit BOT */ + if (!os_mtstate(d)) { /* If fail, try to get OS state */ + d->mta_eof = TRUE; + } + return TRUE; + +#else + return FALSE; +#endif +} + + +int +os_mtclrerr(struct devmt *d) +{ + /* Do MTCSE. Known to be defined only by DECOSF? */ +#if CENV_SYS_UNIX +# if CENV_SYS_SUN || CENV_SYS_SOLARIS || CENV_SYS_BSD || CENV_SYS_LINUX +# ifndef MTCSE /* No equiv, try NOP instead */ +# define MTCSE MTNOP +# endif +# endif + if (!osux_mtop(d, MTCSE, 1, "MTCSE")) { + return FALSE; + } + d->mta_err = 0; + return TRUE; +#else + return FALSE +#endif +} + +void +os_mtshow(struct devmt *d, FILE *f) +{ +#if CENV_SYS_UNIX + struct mtget mtstatb; + + if (ioctl(d->d_fd, MTIOCGET, (char *)&mtstatb) < 0) { + fprintf(f, "; Couldn't get status for %s; %s\r\n", + d->d_path, dp_strerror(-1)); + return; + } + fprintf(f, "; Status for magtape %s:\r\n", d->d_path); + fprintf(f, "; Type: %#x (vals in sys/mtio.h)\r\n", mtstatb.mt_type); +# if CENV_SYS_SUN + fprintf(f, "; Flags: %#o ->", mtstatb.mt_flags); + if (mtstatb.mt_flags & MTF_SCSI) fprintf(f, " SCSI"); + if (mtstatb.mt_flags & MTF_REEL) fprintf(f, " REEL"); + if (mtstatb.mt_flags & MTF_ASF) fprintf(f, " ABSFILPOS"); + fprintf(f, "\r\n"); + fprintf(f, "; Optim blkfact: %d\r\n", mtstatb.mt_bf); +# endif + + fprintf(f, "; Drive status: %#o (dev dep)\r\n", mtstatb.mt_dsreg); + fprintf(f, "; Error status: %#o (dev dep)\r\n", mtstatb.mt_erreg); + fprintf(f, "; Err - Cnt left: %ld\r\n", (long)mtstatb.mt_resid); +# if CENV_SYS_SUN + fprintf(f, "; Err - File num: %ld\r\n", (long)mtstatb.mt_fileno); + fprintf(f, "; Err - Rec num: %ld\r\n", (long)mtstatb.mt_blkno); +# endif + +#endif /* CENV_SYS_UNIX */ +} diff --git a/src/dptm03.h b/src/dptm03.h new file mode 100644 index 0000000..0f7063b --- /dev/null +++ b/src/dptm03.h @@ -0,0 +1,137 @@ +/* DPTM03.H - Device sub-Process defs for TM03 +*/ +/* $Id: dptm03.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1994, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dptm03.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#ifndef DPTM03_INCLUDED +#define DPTM03_INCLUDED 1 + +#ifdef RCSID + RCSID(dptm03_h,"$Id: dptm03.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +#ifndef DPSUP_INCLUDED +# include "dpsup.h" +#endif + +#ifndef DPTM_MAXRECSIZ +# define DPTM_MAXRECSIZ (1L<<16) /* 16 bits worth of record length */ +#endif + +/* Version of DPTM03-specific shared memory structure */ + +#define DPTM03_VERSION DPC_VERSION(1,2,0) /* V1.2.0 */ + + +/* DPTM03-specific stuff */ + /* C = controlling parent sets, D = Device proc sets */ +struct dptm03_s { + struct dpc_s dptm_dpc; /* CD Standard DPC portion */ + int dptm_ver; /* C Version of shared struct */ + int dptm_blkopen; /* C Max times to try blocking open before recheck */ + int dptm_type; /* D Tape type (MTYP_xxx) */ + + /* Tape mount args */ + int dptm_pathx; /* C Index into buffer for pathname */ + int dptm_argsx; /* C Index into buffer for general arg string */ + + /* Tape status */ + int dptm_col; /* CD TRUE if drive came online (cleared by any cmd) */ + int dptm_mol; /* CD TRUE if medium online (tape mounted) */ + int dptm_pip; /* D TRUE if operation in progress */ + int dptm_wrl; /* D TRUE if tape write-locked */ + int dptm_bot; /* D TRUE if BOT seen */ + int dptm_eot; /* D TRUE if EOT seen */ + int dptm_eof; /* D TRUE if EOF (tapemark) seen */ + int dptm_err; /* CD Non-zero if error */ + int dptm_res; /* D Operation result (currently unused) */ + unsigned int + dptm_frms; /* D # frames (bytes) read in record */ + + /* Statistical info */ + int dptm_recs, /* D # recs in tape so far */ + dptm_frecs, /* D # recs in current file so far */ + dptm_files, /* D # files (tapemarks) seen so far */ + dptm_herrs, /* D Hard errors (unrecoverable) */ + dptm_serrs; /* D Soft errors (includes retries) */ + + + /* Record buffer of size DPTM_MAXRECSIZ is allocated after this struct + ** by dp_init(). The following depends on this and reserves space + ** to prefix the buffer with an aligned quantity of zero bytes, so that + ** read-reverse will safely pad out the last word with zeros. + ** See tm_flsbuf() code in dvtm03. + */ + char dptm_revpad[sizeof(double)]; +}; + +#define DPTM_RES_FAIL 0 +#define DPTM_RES_SUCC 1 + +/* Tape device type byte specs + * - ANY + If file doesn't exist, is assumed to be RW virtual and created. + If exists, is checked for type. + Normal file - RW if access allows, else RO. + Special device - hard-mount device + R - Virtual RO + W - Virtual RW + 4 - 4mm + 8 - 8mm +*/ + + +/* Values for d_istape */ +enum dptmmtype { + MTYP_NONE=0, /* Non-mounted */ + MTYP_NULL, /* Null device - special hack */ + MTYP_VIRT, /* Soft-mount: Virtual magtape */ + MTYP_HALF, /* Hard-mount: Half-inch reel magtape */ + MTYP_QIC, /* Quarter-inch cartridge streaming tape */ + MTYP_8MM, /* 8mm cartridge magtape */ + MTYP_4MM /* 4mm DDS/DAT cartridge magtape */ +}; + +/* Commands to and from DP and KLH10 TM03 driver */ + +enum { + DPTM_RESET=0, /* 0 - Reset DP */ + DPTM_MOUNT, /* Mount R/W tape specified by string of N bytes */ + DPTM_ROMNT, /* Mount RO tape specified by string of N bytes */ + + DPTM_NOP, /* No operation */ + DPTM_UNL, /* Unload (implies rewind & unmount) */ + DPTM_REW, /* Rewind tape */ + DPTM_WTM, /* Write Tape Mark (EOF) */ + DPTM_SPF, /* Space N records forward */ + DPTM_SPR, /* Space N records reverse */ + DPTM_SFF, /* Space N files forward */ + DPTM_SFR, /* Space N files reverse */ + DPTM_ER3, /* Erase 3 inches */ + DPTM_WRT, /* Write forward N bytes */ + DPTM_RDF, /* Read forward up to N bytes */ + DPTM_RDR, /* Read reverse up to N bytes */ + DPTM_SNS /* Sense? */ +}; + + +#endif /* ifndef DPTM03_INCLUDED */ diff --git a/src/dvch11.c b/src/dvch11.c new file mode 100644 index 0000000..a4ac9cb --- /dev/null +++ b/src/dvch11.c @@ -0,0 +1,65 @@ +/* DVCH11.C - Emulates CH11 Chaosnet interface for KS10 +*/ +/* $Id: dvch11.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dvch11.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* Just a dummy for now; writes do nothing and reads return 0. +*/ + +#include "klh10.h" + +#if !KLH10_DEV_CH11 && CENV_SYS_DECOSF + /* Stupid gubbish needed to prevent OSF/1 AXP compiler from + ** halting merely because compiled file is empty! + */ +static int decosfcclossage; +#endif + +#if KLH10_DEV_CH11 /* Moby conditional for entire file */ + +#include "kn10def.h" +#include "kn10dev.h" +#include "dvuba.h" +#include "dvch11.h" + +#ifdef RCSID + RCSID(dvch11_c,"$Id: dvch11.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +struct device dvch11; /* Device structure for one CH11 */ + /* Can be static, but left external for debugging */ + + +struct device *dvch11_init(FILE *f, char *s) +{ + iodv_setnull(&dvch11); /* Set up as null device */ + + dvch11.dv_addr = UB_CH11; /* 1st valid Unibus address */ + dvch11.dv_aend = UB_CH11END; /* 1st invalid Unibus address */ + dvch11.dv_brlev = UB_CH11_BR; + dvch11.dv_brvec = UB_CH11_VEC; + + return &dvch11; +} + +#endif /* KLH10_DEV_CH11 */ + diff --git a/src/dvch11.h b/src/dvch11.h new file mode 100644 index 0000000..7185543 --- /dev/null +++ b/src/dvch11.h @@ -0,0 +1,110 @@ +/* DVCH11.H - PDP-11 Chaosnet Interface +*/ +/* $Id: dvch11.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dvch11.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ +/* +** Portions of this file were derived from AI:SYSTEM;CH11 DEFS1 +*/ + +#ifndef DVCH11_INCLUDED +#define DVCH11_INCLUDED 1 + +#ifdef RCSID + RCSID(dvch11_h,"$Id: dvch11.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +extern struct device *dvch11_init(FILE *, char *); + + +/* CH11 addresses & assignments for KS10: + CH11 Address Vector UBA# BR-Level + #1 0764140 0270 3 4 or 5 (dunno which) + etc(?) (some) 0230 +*/ + +#define UB_CH11_BR 5 +#define UB_CH11_VEC 0270 /* CH11 Interrupt Vector */ +#define UB_CH11 0764140 /* CH11 Unibus Address (on UBA #3) */ + +/* CH11 Unibus Chaosnet Interface definitions */ + +#define UB_CHCSR 0764140 /* COMMAND STATUS REG */ +# define CH_BSY 01 /* 0 XMT BUSY (RO) */ +# define CH_LUP 02 /* 1 LOOP BACK (R/W) */ +# define CH_SPY 04 /* 2 RECIEVE MSGS FOR ANY DESTINATION (R/W) */ +# define CH_RCL 010 /* 3 CLEAR THE RECEIVER, IT CAN NOW GOBBLE ANOTHER MSG (WO) */ +# define CH_REN 020 /* 4 RCV INT ENB (R/W) */ +# define CH_TEN 040 /* 5 XMT INT ENB (R/W) */ +# define CH_TAB 0100 /* 6 TRANSMIT ABORTED BY ETHER CONFLICT (RO) */ +# define CH_TDN 0200 /* 7 TRANSMIT DONE. SET WHEN TRANSMITTER IS DONE */ +# define CH_TCL 0400 /* 8 CLEAR THE TRANSMITTER, MAKING IT READY (WO) */ +# define CH_LOS 017000 /* 9-12 LOST COUNT (RO) [# MSGS RCVED WITH RCV BFR FULL] */ +/* ; WHEN MSG IS WAITING IN BUFFER, THIS COUNTS + ; THE MESSAGES THAT MATCHED OUR DESTINATION OR + ; WERE BROADCAST, BUT COULDN'T BE RECIEVED. + ; WHEN RECEIVER IS RE-ENABLED (WRITE 1 INTO %CARDN) + ; THE COUNT IS THEN CLEARED. + ; WHEN A MESSAGE IS LOST, RECEIVER ZAPS ETHER + ; SO TRANSMITTER WILL ABORT (IF MESSAGE WAS DESTINED + ; TO US.) */ +# define CH_RST 020000 /* 13 I/O RESET (WO) */ +# define CH_ERR 040000 /* 14 CRC ERROR (RO) */ +# define CH_RDN 0100000 /* 15 RCV DONE. */ + +#define UB_CHMYN 0764142 /* MY # (READ ONLY) */ + /* RETURNS THE [SOURCE] HOST# OF THIS INTERFACE. */ + +#define UB_CHWBF 0764142 /* WRITE BUFFER (WRITE ONLY) */ + /* FIRST WAIT FOR TDONE. (OR SET IT VIA CSR) + ;FIRST WORD IN RESETS TRANSMITTER AND CLEARS TDONE. + ;STORE INTO THIS REGISTER TO WRITE WORDS OF MESSAGE, + ;LAST WORD IN IS DESTINATION ADDRESS, THEN READ CAIXMT. + ;SOURCE ADDRESS AND CHECK WORD PUT IN BY HARDWARE. */ + +#define UB_CHRBF 0764144 /* READ BUFFER (READ ONLY) */ + /* THE FIRST WORD READ WILL BE FILLED TO THE LEFT + ;TO MAKE THE MESSAGE RECIEVED A MULTIPLE OF 16 BITS. + ;IF THE NUMBER OF DATA BITS IN THE MESSAGE WAS A + ;MULTIPLE OF 16, THIS WORD WILL BE THE FIRST WORD PUT + ;INTO THE BUFFER BY THE TRANSMITTING HOST. + ;THE LAST 3 WORDS READ ARE DESTINATION, SOURCE, CHECK. */ + +#define UB_CHRBC 0764146 /* RECEIVE BIT COUNTER (READ ONLY) */ + /* WHEN A MESSAGE HAS BEEN RECEIVED THIS IS ONE LESS THAN + ;THE NUMBER OF BITS IN THE MESSAGE (16 X THE + ;NUMBER OF WORDS INCLUDING THE THREE OVERHEAD WORDS.) + ;AFTER THE LAST WORD (THE CRC WORD) HAS BEEN READ, IT IS 7777 + ;BITS 10 AND 11 ARE THE HIGH ORDER BITS, AND IF THEY ARE ONE, + ;THEN THERE WAS A BUFFER OVERFLOW */ + +#define UB_CHXMT 0764152 /* READING THIS INITIATES TRANSMISSION (!!) */ + /* THE VALUE READ IS ONE'S OWN HOST#. */ + +/* +;REFERENCING ADDRESSES IN THE GROUP OF 8 WORDS NOT LISTED HERE, OR +;USING COMBINATIONS OF READ/WRITE NOT LISTED HERE, WILL TYPICALLY CAUSE +;STRANGE AND BIZARRE EFFECTS. +*/ + +#define UB_CH11END (UB_CHXMT+2) /* First addr not used by CH11 regs */ + +#endif /* ifndef DVCH11_INCLUDED */ diff --git a/src/dvcty.c b/src/dvcty.c new file mode 100644 index 0000000..219cdb7 --- /dev/null +++ b/src/dvcty.c @@ -0,0 +1,318 @@ +/* DVCTY.C - Support for Console TTY (FE TTY on some machines) +*/ +/* $Id: dvcty.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dvcty.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#include + +#include "klh10.h" +#include "kn10def.h" +#include "kn10ops.h" +#include "dvcty.h" /* Exported functions, etc */ + +#ifdef RCSID + RCSID(dvcty_c,"$Id: dvcty.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Imported functions */ +extern int dte_ctysin(int); + +/* Exported data */ +int cty_debug = 0; /* CTY I/O tracing initially OFF */ + +/* Local functions & data */ +#if KLH10_CPU_KS +static int cty_sin(int cnt); +#endif + +static struct clkent *ctytmr; /* For re-check of CTY input */ + +/* + FE to KS10 communication routines. + + From arduous trial and error it appears that the FE <-> KS10 +interrupt mechanism exists primarily to handle console TTY I/O. + + The FE interrupts the KS10 (sets APRF_FEINT) whenever it has +placed an input character in FECOM_CTYIN. The KS10 then reads this +word, if non-zero, and clears it. The FE won't (or shouldn't) +send another char until that location is zero. +[Actually, TOPS-20 relies on it checking only the "valid" bit, 0400] + + The KS10 clears the APRF_FEINT flag, but since it does this +before reading the input character from FECOM_CTYIN, this event +cannot be used to trigger new input checks. + + The KS10 interrupts the FE (sets APRF_INT80 momentarily) whenever +it has placed an output character in FECOM_CTYOT (033). The FE +picks it up if non-zero, then clears the word. IT THEN INTERRUPTS +THE KS10 BY SETTING APRF_FEINT to signal its readiness for more. +The only thing distinguishing this from the CTY-input case is the +fact that FECOM_CTYIN is clear; however, a single interrupt may +signal both situations (CTY input waiting, and CTY output ready). + + Unfortunately, the same situation is not true for the other (input) +direction, since the KS10 doesn't bother to set any signal flag when +it's gobbled the input character. The FE must continually check to +see if FECOM_CTYIN is clear. Barf. + +Actually, T20 does this a bit differently. It appears that T20 +sends a FE interrupt pulse whenever it changes anything, either +when reading a char from CTYIN (T20 merely clears the valid bit, 0400), +or when a validated char has been deposited in CTYOT. +T20 also expects to receive a KS10 interrupt from the FE whenever +the FE has changed anything - this can indicate either that a new +validated char is in CTYIN, or that a char was removed (and invalidated) +from CTYOT. + +*/ + +/* SIGIO interrupt handler. +** Intention here is that this indicate input available on the TTY +** input fd. +** It needs to read all available input as SIGIO only happens when +** the input state changes from none to some. Sets a flag which remembers +** whether the last check indicated more input or not. +*/ +#if KLH10_CTYIO_INT +static void +cty_isig(int junk) +{ + if ((cpu.fe.fe_ctyinp = os_ttyintest()) <= 0) { + return; + } + INTF_SET(cpu.intf_ctyio); /* Say CTY input available */ + INSBRKSET(); +} +#endif + +/* CTY_CLKTMO - for use when doing without OS I/O interrupts and +** need to explicitly poll the CTY to see if input is available. +** ALSO used when 10 isn't accepting input fast enough, and we need +** to re-check later to see if it can accept more. +** This timeout routine is called at a synchronized (between-instr) point. +*/ +static int +cty_clktmo(void *arg) /* arg unused */ +{ +#if KLH10_CTYIO_INT + cty_isig(0); /* Do a check this way */ + /* NOTE: Can't call cty_incheck as it would hack the + ** timer while already in its callout! + */ + return CLKEVH_RET_QUIET; /* Go quiescent until re-activated */ +#else + cty_incheck(); + return CLKEVH_RET_REPEAT; /* No signals, always stay alive */ +#endif +} + +void +cty_init(void) +{ + /* Set up timeout call once per 1/30 sec -- should be enough. */ + ctytmr = clk_tmrget(cty_clktmo, (void *)NULL, CLK_USECS_PER_SEC/30); + +#if KLH10_CTYIO_INT + clk_tmrquiet(ctytmr); /* Disable timer for now */ + INTF_INIT(cpu.intf_ctyio); + os_ttysig(cty_isig); /* Set up TTY input signal handler */ + cty_isig(0); /* Trigger initial call */ +#endif +} + +void +cty_enable(void) +{ + os_ttyrunmode(); +#if KLH10_CTYIO_INT + cty_isig(0); /* Trigger initial test */ +#endif +} + +void +cty_disable(void) +{ + os_ttyreset(); +} + +/* CTY_INCHECK - Invoked either directly by timeout or indirectly by host-OS +** signal. +** Checks to see if TTY input is available, and if so does what's +** necessary to get it from host-OS and feed it to the 10. +*/ +void +cty_incheck(void) +{ + register int inpend; + + if ((inpend = cpu.fe.fe_ctyinp) > 0 /* See if any input waiting */ + || (inpend = os_ttyintest()) > 0) { /* Think not, but check OS */ + + /* Get input string. If didn't get it all, activate timer to check + ** again later to see if 10 is accepting input. + */ +#if KLH10_CPU_KS + if (((inpend = cty_sin(inpend)) > 0) +#elif KLH10_CPU_KL + if (((inpend = dte_ctysin(inpend)) > 0) +#endif + || (inpend = os_ttyintest()) > 0) { + clk_tmractiv(ctytmr); /* Ensure timer active */ + if (cty_debug) + fprintf(stderr, "[cty tmract: %d]", inpend); + } + } + cpu.fe.fe_ctyinp = inpend; +} + +#if KLH10_CPU_KS + /* Rest of code is all for KS */ + +static int +cty_sin(int cnt) +{ + register vmptr_t vp; + register int ch, oldch; + + if ((ch = os_ttyin()) < 0) /* Get single char */ + return 0; /* None left */ + + vp = vm_physmap(FECOM_CTYIN); + oldch = vm_pgetrh(vp); /* See if ready for next char */ + if (oldch & 0400) + fprintf(stderr, "[CTYI: %o => %o, old %o]", + ch, ch | 0400, oldch); + else if (cty_debug) + fprintf(stderr, "[CTYI: %o]", ch); + + /* Drop char in FE communication area */ + vm_psetxwd(vp, 0, (ch |= 0400)); + + /* Send interrupt to the KS10 saying stuff is there */ + cpu.aprf.aprf_set |= APRF_FEINT; /* Crock for now */ + apr_picheck(); + + return cnt-1; /* Only one char at a time, sigh */ +} + + +/* FE_INTRUP - Called from WRAPR (CONO PI,) when a FE interrupt is requested +** by the KS10 - the KS10 has changed something in the com area. +** +** Note gross hack here to delay output for a T20 KS. +** Apparently, T20 can't cope with the fact that the KLH10 responds +** "instantaneously", generating a FE->KS interrupt to acknowledge that +** the output char has been gobbled. The T20 code isn't prepared for +** this until it has executed something like 50 more instructions; it +** will handle the interrupt, but then thinks it needs to wait for +** an interrupt (not knowing that it already arrived and was handled!) +** and stalls. +** Two workarounds are supported. The first uses fe_iowait +** (called "cty_iowait" in the UI) to delay at least 50 instructions +** after gobbling output before giving T20 its interrupt. This works, +** but slows things a lot when using a real-time clock emulation. +** The second uses cty_lastint to give T20 an extra interrupt +** some amount of time after the last output char of a bufferful has +** gone out. This is more efficient. +** +** Obviously the right thing is to fix T20, but we have to support +** unmodified monitor binaries. +*/ +void +fe_intrup(void) +{ + if (cpu.fe.fe_iowait) { /* If config setting is for delay, */ + cpu.io_ctydelay = cpu.fe.fe_iowait; /* Start counter */ + return; + } + cty_timeout(); /* Else hack it immediately */ +} + + +# if KLH10_CTYIO_ADDINT +static int cty_lasttmo(void *junk); +# endif + +/* CTY_TIMEOUT - Gross hack to delay output so that KS T20 +** can catch up. +*/ +void +cty_timeout(void) +{ + register int c; + register vmptr_t vp = vm_physmap(FECOM_CTYOT); /* Find phys loc */ + + /* Check special memory location to see what's there. */ + c = vm_pgetrh(vp); /* Get RH of physical loc */ + if (c & 0400) { /* Valid char? */ + c &= 0377; + if (cty_debug) fprintf(stderr, "[CTYO: %o]", c); + os_ttyout(c); + + /* Done, now send interrupt to KS10 saying output ready */ + cpu.aprf.aprf_set |= APRF_FEINT; + /* Can skip apr_picheck() as caller will do it */ +# if KLH10_CTYIO_ADDINT + /* If our config wants an additional interrupt later, ensure it's + set up. This looks hairy only because it tries to notice when user + modifies interval and changes its timer accordingly. + */ + if (cpu.fe.cty_lastint) { /* If we want since-last-char timer */ + if (!cpu.fe.cty_lastclk) /* Get it if first time */ + cpu.fe.cty_lastclk = clk_tmrget(cty_lasttmo, (void *)NULL, + (int32)(cpu.fe.cty_lastint * 1000)); + else if (cpu.fe.cty_prevlastint != cpu.fe.cty_lastint) { + clk_tmrquiet(cpu.fe.cty_lastclk); /* Changed interval! */ + clk_tmrset(cpu.fe.cty_lastclk, + (int32)(cpu.fe.cty_lastint * 1000)); + } else { + clk_tmrquiet(cpu.fe.cty_lastclk); /* Restart timer */ + clk_tmractiv(cpu.fe.cty_lastclk); /* using same interv */ + } + cpu.fe.cty_prevlastint = cpu.fe.cty_lastint; /* Remember interval*/ + } else if (cpu.fe.cty_lastclk) { /* Don't want, ensure clear */ + clk_tmrkill(cpu.fe.cty_lastclk); + cpu.fe.cty_lastclk = NULL; + } +# endif + } + vm_psetxwd(vp, 0, 0); /* Clear word */ +} + +# if KLH10_CTYIO_ADDINT +/* CTY_LASTTMO - Timeout that sends a spurious FE interrupt to the 10 a +** certain length of time after the *last* output character of a series. +** This is an alternative to fe_iowait for getting around the T20 +** monitor bug that loses interrupts on the KLH10's "instantaneous" +** CTY output. +*/ +static int +cty_lasttmo(void *junk) +{ + cpu.aprf.aprf_set |= APRF_FEINT; /* Give CPU a spurious CTY int */ + apr_picheck(); + return CLKEVH_RET_QUIET; /* Go quiescent afterwards */ +} +# endif /* KLH10_CTYIO_ADDINT */ + +#endif /* KLH10_CPU_KS */ diff --git a/src/dvcty.h b/src/dvcty.h new file mode 100644 index 0000000..21bcf36 --- /dev/null +++ b/src/dvcty.h @@ -0,0 +1,48 @@ +/* DVCTY.H - Support for Console TTY (FE TTY on some machines) +*/ +/* $Id: dvcty.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dvcty.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#ifndef DVCTY_INCLUDED +#define DVCTY_INCLUDED 1 + +#ifdef RCSID + RCSID(dvcty_h,"$Id: dvcty.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Exported functions */ + +extern void cty_init(void); +extern void cty_enable(void); +extern void cty_disable(void); +extern void cty_incheck(void); + +#if KLH10_CPU_KS +extern void fe_intrup(void); /* Move/rename this */ +extern void cty_timeout(void); +#endif + +/* Exported data */ + +extern int cty_debug; + +#endif /* ifndef DVCTY_INCLUDED */ diff --git a/src/dvdte.c b/src/dvdte.c new file mode 100644 index 0000000..9bdae11 --- /dev/null +++ b/src/dvdte.c @@ -0,0 +1,2353 @@ +/* DVDTE.C - Emulates DTE20 10/11 interface for KL10 +*/ +/* $Id: dvdte.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dvdte.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#include "klh10.h" + +#if KLH10_DEV_DTE /* Moby conditional for entire file */ + +#include /* For size_t etc */ +#include +#include +#include + +#include "kn10def.h" /* This includes OSD defs */ +#include "kn10ops.h" +#include "kn10dev.h" +#include "dvdte.h" +#include "prmstr.h" /* For parameter parsing */ + +#ifdef RCSID + RCSID(dvdte_c,"$Id: dvdte.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Internal DTE packet for RSX20F protocol */ + +struct dtepkt_s { + /* These are all 16-bit values */ + unsigned int dtep_tcnt; /* Total byte count of entire msg */ + + unsigned int dtep_cnt; /* byte count from header field */ + unsigned int dtep_fn; /* function # */ + unsigned int dtep_dev; /* Device */ + unsigned char dtep_db0; /* First data byte */ + unsigned char dtep_db1; /* Second data byte */ + + int dtep_wdmod; /* TRUE if rest of data in word mode */ + + unsigned char dtep_data[256]; +}; + +struct dteq_s { + struct dteq_s *q_next; + + unsigned int q_sfn; /* Swapped function # */ + unsigned int q_sdev; /* Swapped Device */ + unsigned int q_swd1; /* Swapped First data word */ + + int q_bbcnt; /* Direct byte data byte count */ + int q_wbcnt; /* Indirect word data byte count */ + unsigned char *q_dcp; /* Ptr to data (ignored if both counts 0) */ +}; + +/* Swap bytes */ +#define SWAB(i) ((((i)>>8)&0377) | (((i)&0377)<<8)) + +#define SFN_INDBIT SWAB(1<<15) /* High bit of function is @ bit */ + +#define DTE_NQNODES 30 /* Maybe should be dynamic param */ +struct dteq_s *dteqfreep = NULL; +struct dteq_s dteqnodes[DTE_NQNODES]; + + +struct dte { + struct device dt_dv; /* Generic 10 device structure */ + + /* DTE-specific vars */ + int dt_dten; /* # of this DTE (0-3) */ + unsigned int dt_cond; /* Condition bits (only 16 needed) */ + int dt_pilev; /* PI level bit mask */ + w10_t dt_piwd; /* PI vector word (fixed after inited) */ + + int dt_dowarn; /* TRUE to print warnings (semi-debug) */ + int dt_ismaster; /* TRUE if this DTE is master */ + int dt_secptcl; /* TRUE if using secondary ptcl + ** (monitor mode), else primary */ + int dt_reld11; /* 11 Reload button */ + int dt_dtmsent; /* TRUE if date/time sent to 10. */ + + /* EPT shadow vars - only updated when entering primary ptcl */ + vmptr_t dt_eptcb; /* VM pointer to DTE ctl blk in EPT */ + unsigned int dt_eptoff; /* Offset into EPT for this DTE */ + unsigned int dt_eptesz; /* Examine protection (size of area) */ + vmptr_t dt_eptexa; /* Examine Relocation */ + unsigned int dt_eptdsz; /* Deposit protection (size of area) */ + vmptr_t dt_eptdep; /* Deposit Relocation */ + + /* Comm region vars (11 side) + ** These are primarily offsets from the DTE20's examine relocation addr. + ** Use the proper variable to access the corresponding com region. + ** Note special case dt_dt10off which is only used for DEPOSIT and + ** refers to the same place that dt_et10off reads. + */ + /* FE var */ + int dt_procn; /* PRMEMN Our processor # */ + + unsigned int dt_combase; /* COMBSE Exa offset to base */ + unsigned int dt_doff; /* DEPOF Exa offset to our 11's comdat */ + /* == Offset from exa reloc to dep reloc */ + /* i.e. refs same loc as DTE dep reloc! */ + unsigned int dt_et10off; /* EMYN Exa off to our 11's to-10 comrgn */ + unsigned int dt_dt10off; /* DMYN DEP off to our 11's " " */ + unsigned int dt_ec10off; /* EHSG Exa off to 10's general comdat */ + unsigned int dt_et11off; /* EHSM Exa off to 10's to-our-11 comrgn */ + + + /* Stuff for active to-10 (send to 10) xfer */ + int dt_snd_ibit; /* To-10 "I" bit, set by DATAO */ + int dt_snd_cnt; /* To-10 byte cnt, set by DATAO */ + w10_t dt_snd_sts; /* To-10 Status word (internal copy) */ + int dt_snd_state; /* To-10 sending state */ +# define DTSND_HDR 0 /* Send header next */ +# define DTSND_DAT 1 /* Sending data bytes */ +# define DTSND_IND 2 /* Sending indirect data */ + struct dteq_s *dt_sndq; /* To-10 send queue */ + struct dteq_s *dt_sndqtail; /* To-10 send queue tail ptr */ + struct dtepkt_s dt_sndpkt; + + + /* Stuff for active to-11 (receive from 10) xfer */ + int dt_rcv_gothdr; /* TRUE if header received */ + int dt_rcv_indir; /* TRUE if in indirect xfer */ + int dt_rcv_11qc; /* Current to-11 queue count */ + struct dtepkt_s dt_rcvpkt; + + /* New clock timer stuff */ + struct clkent *dt_kpal; /* Timer for keepalive update */ + + int32 dt_dlyackms; /* Delayed ACK timeout in msec */ + struct clkent *dt_dlyack; /* Timer for Delayed ACK */ + int dt_dlyackf; /* TRUE if timer active */ + + /* KLDCP stuff, mostly clock */ + int dt_clkf; /* 0=off, 1=on, 2=on with countdown */ + struct clkent *dt_clk; /* Timer for 60Hz ticks */ + uint32 dt_clkticks; /* # ticks since last enabled */ + int32 dt_clkcnt; /* # ticks left before interrupt 10 */ + + /* CTY buffer stuff - just output for now */ + int dt_ctyobs; /* Desired output buffer size to tell 10 */ + int dt_ctyocnt; /* # chars room left in buffer */ + char *dt_ctyocp; /* Deposit pointer */ + char dt_ctyobuf[128]; +}; + +static int ndtes = 0; +struct dte dvdte[DTE_NSUP]; /* Device structs for DTE units */ + +/* Internal predeclarations */ + +static int dte_conf(FILE *f, char *s, struct dte *dt); +static void dte_picheck(struct dte *dt); +static int dte_prim(struct dte *dt); +static int dte_kaltmo(void *arg); +static int dte_acktmo(void *arg); + +static void dte_11db(struct dte *dt); +static void dte_dosecp(struct dte *dt); +static void dte_dbprmp(struct dte *dt); +static void dte_11done(struct dte *dt); +static void dte_rdhd(struct dte *dt); +static void dte_rdbyte(struct dte *dt, int cnt, unsigned char *ucp); +static void dte_rdword(struct dte *dt, int cnt, unsigned char *ucp); +static void dte_wrhd(struct dte *dt, struct dteq_s *q); +static void dte_wrbyte(struct dte *dt, struct dteq_s *q, int cnt); +static void dte_wrword(struct dte *dt, struct dteq_s *q, int wcnt); +static void dte_xctfn(struct dte *dt); +static void dte_showpkt(struct dte *dt, struct dtepkt_s *dtp); +static void dte_10start(struct dte *dt); +static void dte_10xfrbeg(struct dte *dt); +static void dte_11xfrdon(struct dte *dt); +static int dte_10qpkt(struct dte *dt, + unsigned int sfn, unsigned int sdev, + unsigned int swd1, int bbcnt, int wbcnt, + unsigned char *dcp); +static void dte_dtmsend(struct dte *dt); +static void dte_ctyack(struct dte *dt); +static void dte_ctyiack(struct dte *dt); +static void dte_ctyout(struct dte *dt, int ch); +static void dte_ctysout(struct dte *dt, unsigned char *cp, int len); +static void dte_ctyforce(struct dte *dt); +static void dte_clkset(struct dte *dt, int on); +static int dte_clktmo(void *arg); + +static int dt_init(struct device *d, FILE *of); +static w10_t dt_pifnwd(struct device *d); +static void dt_cono(struct device *d, h10_t erh); +static w10_t dt_coni(struct device *d); +static void dt_datao(struct device *d, w10_t w); +static w10_t dt_datai(struct device *d); +#if 0 +static int dt_readin(); /* Readin boot, if supported */ +#endif + +/* Configuration Parameters */ + +#define DVDTE_PARAMS \ + prmdef(DTP_DBG, "debug"), /* Initial debug value */\ + prmdef(DTP_MSTR, "master"), /* This is master DTE (default FALSE) */\ + prmdef(DTP_ADLY, "ackdly"), /* ACK delay in msec (default 0) */\ + prmdef(DTP_OBS, "obs"), /* Output buffer alloc (default 64) */\ + prmdef(DTP_WARN, "warn"), /* Show warnings (default FALSE) */\ + prmdef(DTP_DPDBG,"dpdebug"), /* Initial DP debug value (for later) */\ + prmdef(DTP_DP, "dppath") /* Device subproc pathname (for later) */ + +enum { +# define prmdef(i,s) i + DVDTE_PARAMS +# undef prmdef +}; + +static char *dtprmtab[] = { +# define prmdef(i,s) s + DVDTE_PARAMS +# undef prmdef + , NULL +}; + + +/* DTE_CONF - Parse configuration string and set defaults. +** At this point, device has just been created, but not yet bound +** or initialized. +** NOTE that some strings are dynamically allocated! Someday may want +** to clean them up nicely if config fails or device is uncreated. +*/ +static int dte_conf(FILE *f, char *s, struct dte *dt) +{ + int i, ret = TRUE; + struct prmstate_s prm; + char buff[200]; + long lval; + + /* First set defaults for all configurable parameters */ + DVDEBUG(dt) = FALSE; + dt->dt_ismaster = FALSE; + dt->dt_dlyackms = 0; + dt->dt_dowarn = FALSE; + dt->dt_ctyobs = sizeof(dt->dt_ctyobuf)/2; +#if KLH10_DEV_DPDTE + dt->dt_dpname = "dpdte"; /* Pathname of device subproc */ + dt->dt_dtdbg = FALSE; +#endif + + prm_init(&prm, buff, sizeof(buff), + s, strlen(s), + dtprmtab, sizeof(dtprmtab[0])); + while ((i = prm_next(&prm)) != PRMK_DONE) { + switch (i) { + case PRMK_NONE: + fprintf(f, "Unknown DTE parameter \"%s\"\n", prm.prm_name); + ret = FALSE; + continue; + case PRMK_AMBI: + fprintf(f, "Ambiguous DTE parameter \"%s\"\n", prm.prm_name); + ret = FALSE; + continue; + default: /* Handle matches not supported */ + fprintf(f, "Unsupported DTE parameter \"%s\"\n", prm.prm_name); + ret = FALSE; + continue; + + case DTP_DBG: /* Parse as true/false boolean or number */ + if (!prm.prm_val) /* No arg => default to 1 */ + DVDEBUG(dt) = 1; + else if (!s_tobool(prm.prm_val, &DVDEBUG(dt))) + break; + continue; + + case DTP_ADLY: /* Parse as decimal number */ + if (!prm.prm_val || !s_todnum(prm.prm_val, &lval)) + break; + dt->dt_dlyackms = lval; + continue; + + case DTP_OBS: /* Parse as decimal number */ + if (!prm.prm_val || !s_todnum(prm.prm_val, &lval)) + break; + if (lval < 0 || lval > 0xFF) /* Ensure an 8-bit value */ + lval = 0xFF; + dt->dt_ctyobs = lval; + continue; + + case DTP_MSTR: /* Parse as true/false boolean */ + if (!prm.prm_val) { /* No arg => default to TRUE */ + dt->dt_ismaster = TRUE; + continue; + } + if (!s_tobool(prm.prm_val, &dt->dt_ismaster)) + break; + continue; + + case DTP_WARN: /* Parse as true/false boolean */ + if (!prm.prm_val) { /* No arg => default to TRUE */ + dt->dt_dowarn = TRUE; + continue; + } + if (!s_tobool(prm.prm_val, &dt->dt_dowarn)) + break; + continue; + +#if KLH10_DEV_DPDTE + case DTP_DPDBG: /* Parse as true/false boolean or number */ + if (!prm.prm_val) /* No arg => default to 1 */ + dt->dt_dpdbg = 1; + else if (!s_tobool(prm.prm_val, &(dt->dt_dpdbg))) + break; + continue; + + case DTP_DP: /* Parse as simple string */ + if (!prm.prm_val) + break; + dt->dt_dpname = s_dup(prm.prm_val); + continue; +#endif + } + ret = FALSE; + fprintf(f, "DTE param \"%s\": ", prm.prm_name); + if (prm.prm_val) + fprintf(f, "bad value syntax: \"%s\"\n", prm.prm_val); + else + fprintf(f, "missing value\n"); + } + + /* Param string all done, do followup checks */ + + return ret; +} + +struct device * dvdte_create(FILE *f, char *s) +{ + register struct dte *dt; + static int onceinit = 0; + + if (!onceinit) { + /* Do once-only global init */ + register int i; + register struct dteq_s *q; + + for (q = dteqnodes, i = DTE_NQNODES; --i > 0; ++q) + q->q_next = q+1; + q->q_next = NULL; /* Last node's next is 0 */ + dteqfreep = dteqnodes; /* Start of freelist */ + + onceinit = TRUE; + } + + /* Parse string to determine which DTE to use, config, etc etc + ** But for now, just allocate sequentially. Hack. + */ + if (ndtes >= DTE_NSUP) { + fprintf(f, "Too many DTEs, max: %d\n", DTE_NSUP); + return NULL; + } + dt = &dvdte[ndtes]; /* Pick unused DTE */ + memset((char *)dt, 0, sizeof(*dt)); /* Clear it out */ + dt->dt_dten = ndtes++; /* Remember its number */ + + /* Initialize generic device part of DTE struct */ + iodv_setnull(&dt->dt_dv); /* Initialize as null device */ + dt->dt_dv.dv_pifnwd = dt_pifnwd; + dt->dt_dv.dv_cono = dt_cono; + dt->dt_dv.dv_coni = dt_coni; + dt->dt_dv.dv_datao = dt_datao; + dt->dt_dv.dv_datai = dt_datai; + + dt->dt_dv.dv_init = dt_init; + + if (!dte_conf(f, s, dt)) /* Do configuration stuff */ + return NULL; + + return &dt->dt_dv; +} + + +static int dt_init(struct device *d, FILE *of) +{ + register struct dte *dt = (struct dte *)d; + + dt->dt_cond = 0; /* Clear all CONI bits */ + dt->dt_pilev = 0; + dt->dt_eptoff = DTE_CB(dt->dt_dten); /* Find its EPT offset */ + dt->dt_eptesz = dt->dt_eptdsz = 0; /* No exa/dep areas yet */ + LRHSET(dt->dt_piwd, PIFN_FVEC, /* Build vectored PI fn wd */ + dt->dt_eptoff + DTE_CBINT); + + if (dt->dt_ismaster) { /* If this one is master, */ + dt->dt_secptcl = TRUE; + } else { + dt->dt_cond |= DTE_CIRM; /* say restricted, not master DTE */ + dt->dt_secptcl = FALSE; + } + dt->dt_dtmsent = FALSE; + + dt->dt_ctyocnt = sizeof(dt->dt_ctyobuf); /* Reset CTY output buffer */ + dt->dt_ctyocp = dt->dt_ctyobuf; + + /* Stuff that belongs in a "dt_11reboot" section */ + + /* Note the setting of the to-10 status word is done entirely from + ** the FE and any existing value in the 10 is ignored. At boot + ** time we start with all counts clear (particularly TO10IC, which is + ** bumped each time we send something to the 10). + */ + LRHSET(dt->dt_snd_sts, DTE_CMT_TST|DTE_CMT_QP, 0); + + /* Set up timer to bump keepalive every 1/2 sec. */ + if (!dt->dt_kpal) + dt->dt_kpal = clk_tmrget(dte_kaltmo, (void *)dt, + CLK_USECS_PER_SEC/2); + + /* Set up ACK delay timer (mostly useful for T10) */ + if (dt->dt_dlyackms) { + dt->dt_dlyack = clk_tmrget(dte_acktmo, (void *)dt, + dt->dt_dlyackms * 1000); + clk_tmrquiet(dt->dt_dlyack); /* Immediately make it quiescent */ + dt->dt_dlyackf = FALSE; + } + + return TRUE; +} + +/* PI: Get function word +** This has the potential to get really hairy depending on how closely +** we want to emulate what the DTE actually does. +** For now, never emulate the exa/dep or byte xfer PI0 functions. +** Only handle normal vectoring through the EPT word. +*/ +static w10_t dt_pifnwd(struct device *d) +{ + return ((struct dte *)d)->dt_piwd; +} + +/* CONO 18-bit conds out +** Args D, ERH +** Returns nothing +*/ +static insdef_cono(dt_cono) +{ + register struct dte *dt = (struct dte *)d; + register unsigned int cond = dt->dt_cond; + + if (dt->dt_dv.dv_debug) { + fprintf(dt->dt_dv.dv_dbf, "[DTE: Cono %lo", (long)erh); + if ((erh & DTE_COPIENB) && !(erh & DTE_CIPI)) { + fputs(" (PI off)", dt->dt_dv.dv_dbf); + } + fputs("]\r\n", dt->dt_dv.dv_dbf); + } + + if (erh & DTE_COPIENB) { /* Enabling PI? */ + cond &= ~(DTE_CIPI0|DTE_CIPI); /* Clear old cond bits */ + cond |= erh & (DTE_CIPI0|DTE_CIPI); /* And insert new bits */ + + /* If changing PI assignment, do tricky stuff */ + dt->dt_pilev = (1 << (7-(cond & DTE_CIPI))) & 0177; /* 0 if PIA=0 */ + if (dt->dt_dv.dv_pireq && (dt->dt_dv.dv_pireq != dt->dt_pilev)) { + /* Changed PIA while PI outstanding; flush, let picheck re-req */ + (*dt->dt_dv.dv_pifun)(&dt->dt_dv, 0); /* clear it. */ + } + + /* Check PI now or at end */ + } + if (erh & DTE_COCL11) { /* Clearing TO11DN and TO11ER? */ + cond &= ~(DTE_CI11DN|DTE_CI11ER); + } + if (erh & DTE_COCL10) { /* Clearing TO10DN and TO10ER? */ + cond &= ~(DTE_CI10DN|DTE_CI10ER); + } + if (erh & DTE_CO10DB) { /* Clearing TO10DB doorbell? */ + cond &= ~(DTE_CI10DB); + } + + if (erh & DTE_COCR11) { /* Clearing reload-11 button? */ + dt->dt_reld11 = 0; + } + if (erh & DTE_COSR11) { /* Setting reload-11 button? */ + dt->dt_reld11 = -1; + } + + dt->dt_cond = cond; + + if (erh & DTE_CO11DB) { /* Requesting TO11DB doorbell? */ + dt->dt_cond |= DTE_CI11DB; /* Turn doorbell on */ + dte_11db(dt); /* Invoke virtual PDP-11 */ + } + + dte_picheck(dt); /* Check for possible PI changes */ +} + +/* CONI 36-bit conds in +** Args D +** Returns condition word +*/ +static insdef_coni(dt_coni) +{ + register w10_t w; + LRHSET(w, 0, ((struct dte *)d)->dt_cond); + + if (((struct dte *)d)->dt_dv.dv_debug) { + fprintf(((struct dte *)d)->dt_dv.dv_dbf, + "[DTE: Coni= %lo]\r\n", (long)RHGET(w)); + } + return w; +} + +/* DATAO word out +** Args D, W +** Returns nothing. +** This sets up the receive byte/word count for transfers to +** the 10 from the 11, and starts xfer. +** Also sets up a termination flag. +*/ +static insdef_datao(dt_datao) +{ + register struct dte *dt = (struct dte *)d; + register unsigned int rh = RHGET(w); /* Get RH, OK to truncate */ + + if (dt->dt_dv.dv_debug) { + fprintf(dt->dt_dv.dv_dbf, "[DTE: Datao %lo,,%lo]\r\n", + (long)LHGET(w), (long)RHGET(w)); + } + + dt->dt_snd_ibit = rh & DTE_TO10IB; /* Set "I" bit either on or off */ + + /* Get positive byte cnt */ + dt->dt_snd_cnt = -((rh & DTE_TO10BC) | ~DTE_TO10BC); + + dte_10xfrbeg(dt); /* Start transfer */ +} + +/* DATAI word in +** Args D +** Returns data word. For the DTE this is always 0. +*/ +static insdef_datai(dt_datai) +{ + register w10_t w; + op10m_setz(w); + return w; +} + +/* DTE_PI - trigger PI for selected DTE. +** This could perhaps be an inline macro, but for now +** having it as a function helps debug. +*/ +static void dte_pi(register struct dte *dt) +{ + if (dt->dt_dv.dv_debug) { + fprintf(dt->dt_dv.dv_dbf, "[dte_pi: %o]", dt->dt_pilev); + } + + if (dt->dt_pilev /* If have non-zero PIA */ + && !(dt->dt_dv.dv_pireq)) { /* and not already asking for PI */ + (*dt->dt_dv.dv_pifun)(&dt->dt_dv, dt->dt_pilev); /* then do it! */ + } +} + +/* DTE_PICHECK - Check DTE conditions to see if PI should be attempted. +*/ +static void dte_picheck(register struct dte *dt) +{ + /* If any possible interrupt bits are set */ + if (dt->dt_cond & (DTE_CI10DB + | DTE_CI11DN | DTE_CI11ER + | DTE_CI10DN | DTE_CI10ER)) { + dte_pi(dt); + return; + } + /* Here, shouldn't be requesting PI, so if our request bit is set, + ** turn it off. + */ + if (dt->dt_dv.dv_pireq) { /* If set while shouldn't be, */ + (*dt->dt_dv.dv_pifun)(&dt->dt_dv, 0); /* Clear it! */ + } +} + + +/* Auxiliaries for doing Examine & Deposit via DTE reloc specs in EPT. +** +*/ + +#define dte_fetch(dt, off) \ + ((dt)->dt_eptesz > (off) \ + ? vm_pget((dt)->dt_eptexa + (off)) \ + : dte_badexa(dt, off)) + +#define dte_store(dt, off, w) \ + ((dt)->dt_eptdsz > (off) \ + ? (void)vm_pset((dt)->dt_eptdep + (off), (w)) \ + : dte_baddep(dt, off, w)) + + +/* DTE_EPTUPD - Update internal vars with latest EPT stuff +*/ +static void dte_eptupd(register struct dte *dt) +{ + register vmptr_t vp; + register w10_t w; + register paddr_t pa; + + vp = vm_physmap(cpu.mr_ebraddr + dt->dt_eptoff); + dt->dt_eptcb = vp; /* Remember ptr to CB */ + dt->dt_eptesz = vm_pgetrh(vp+DTE_CBEPW); /* Exa prot (size of area) */ + dt->dt_eptdsz = vm_pgetrh(vp+DTE_CBDPW); /* Dep prot (size of area) */ + + w = vm_pget(vp+DTE_CBERW); /* Get exam reloc word */ + pa = ((LHGET(w)<dt_eptexa = vm_physmap(pa); /* Examine Relocation */ + + w = vm_pget(vp+DTE_CBDRW); /* Get exam reloc word */ + pa = ((LHGET(w)<dt_eptdep = vm_physmap(pa); /* Deposit Relocation */ +} + +static w10_t dte_badexa(register struct dte *dt, + unsigned int off) +{ + register w10_t w; + + fprintf(dt->dt_dv.dv_dbf, "[DTE: Bad exa %o (prot=%o)]", + off, dt->dt_eptesz); + op10m_setz(w); + return w; +} + +static void dte_baddep(register struct dte *dt, + unsigned int off, + register w10_t w) +{ + fprintf(dt->dt_dv.dv_dbf, "[DTE: Bad dep %o (prot=%o)]", + off, dt->dt_eptdsz); +} + +/* Special hack for keep-alive counter. +** Should be invoked by some clock interrupt function. +*/ +int dte_kaltmo(void *arg) +{ + register struct dte *dt = (struct dte *)arg; + register w10_t w; + + if (dt->dt_secptcl) /* Only update if in primary ptcl */ + return CLKEVH_RET_REPEAT; + + /* In primary ptcl, do keepalive update. + ** This code assumes that the DTE deposit relocation points to the + ** start of the 11's own (comdat) region. + */ + if (dt->dt_eptdsz <= DTE_CMOW_KAC) /* Check deposit protection */ + return CLKEVH_RET_REPEAT; + + w = vm_pget(dt->dt_eptdep + DTE_CMOW_KAC); + op10m_inc(w); /* Bump the counter */ + vm_pset(dt->dt_eptdep + DTE_CMOW_KAC, w); /* Store it back */ + + return CLKEVH_RET_REPEAT; +} + + +/* Delayed-ACK timeout routine. +** According to LWS, "instanteous" cty acks from emulated dte causes +** a race condition which TOPS-10 will never win. +** (Output does work, but works much better with a slight device delay to +** avoid the race). +*/ +static int dte_acktmo(void *arg) +{ + register struct dte *dt = (struct dte *)arg; + + if (dt->dt_dlyackf) { + dte_ctyiack(dt); /* Do immediate ACK now */ + dt->dt_dlyackf = FALSE; + } + return CLKEVH_RET_QUIET; /* Go quiescent after firing */ +} + +/* DTE_PRIM - Enter primary protocol (RSX20F) +** This depends on a lot of assumptions about the protocol +** which are documented elsewhere (dvdte.h, dte.doc) +** +** Assumes our local EPT vars are set up. +*/ + +static int dte_prim(register struct dte *dt) +{ + register w10_t w; + + dte_eptupd(dt); /* Re-init from EPT vars! */ + + /* First attempt to read word 0 of our examine area. This should + ** be a word in COMPTR format. + */ + w = dte_fetch(dt, 0); /* Get first word */ + if (op10m_skipe(w)) { + fprintf(dt->dt_dv.dv_dbf, "[DTE: Zero COMPTR?]"); + return 0; + } + + dt->dt_procn = (LHGET(w)>>6)&037; /* Find our processor # */ + dt->dt_combase = DTE_CMBAS(w); /* Derive offset to base */ + + /* For now, cheat a bit while deriving offsets, rather than + ** calculating the sizes etc from the com region data. + */ + + /* Find offset to our 11's comdat (fixed) region. + ** By conspiracy this will be the same as our deposit reloc 0. + */ + dt->dt_doff = dt->dt_combase + (RHGET(w) & MASK16); + + dt->dt_dt10off = 16; /* Comdat is 16 wds; comrgn follows */ + dt->dt_et10off = dt->dt_doff + 16; /* " */ + + /* This cheats by assuming combase is same as the 10's comdat region, + ** instead of deriving it from CMPPT field. + */ + dt->dt_ec10off = dt->dt_combase; + dt->dt_et11off = dt->dt_ec10off + 16 + (8 * dt->dt_dten); + + /* Initialize internal transfer vars */ + dt->dt_rcv_gothdr = FALSE; + dt->dt_rcv_indir = FALSE; + +#if 0 + /* Buggy -- don't clobber each time we re-enter primary ptcl! */ + + /* Should this be inited from 10's memory? No; FE doesn't. */ + LRHSET(dt->dt_snd_sts, DTE_CMT_TST|DTE_CMT_QP, 0); +#endif + return 1; +} + +/* Doorbell rung to virtual 11, initiate some action. +** +*/ + +static void dte_11db(register struct dte *dt) +{ + /* Wake up and see what needs to be done */ + + if (dt->dt_dv.dv_debug) + fprintf(dt->dt_dv.dv_dbf, "[DTE%d: 11DB]", dt->dt_dten); + + if (dt->dt_secptcl) + dte_dosecp(dt); /* Do secondary protocol */ + else { + dte_dbprmp(dt); /* Do primary */ + if (dt->dt_secptcl) /* If failed and switched, then */ + dte_dosecp(dt); /* do secondary protocol instead */ + } + + dt->dt_cond &= ~DTE_CI11DB; /* Routine should turn off 11DB! */ +} + +/* Do secondary protocol stuff +** All refs to EPT must use true current EPT address. +*/ +static void dte_dosecp(register struct dte *dt) +{ + register w10_t w; + + w = vm_pget(vm_physmap(cpu.mr_ebraddr + DTEE_CMD)); + switch (RHGET(w) & DTECMDF_CMD) { + default: + fprintf(dt->dt_dv.dv_dbf, "[DTECMD: unknown %lo]\r\n", + (long)RHGET(w)); + + /* RSX20F interprets all unrecognized secondary ptcl cmds as + ** "output char". So drop through. + */ + + case DTECMD_MNO: /* Output char in monitor mode */ + if (dt->dt_dv.dv_debug) + fprintf(dt->dt_dv.dv_dbf, "[DTECMD:O %o]",(int)RHGET(w)&0377); + os_ttyout((int)RHGET(w)&0377); + + op10m_seto(w); /* Set TMD flag -1 to confirm "done" */ + + /* Actually this should be a secondary-ptcl feature mask! + ** See T10 DTEPRM for details of bits. Low bit 0 means support + ** cmd 13 (get date/time). + */ + op10m_trz(w, 01); /* Say cmd 13 now supported! */ + vm_pset(vm_physmap(cpu.mr_ebraddr + DTEE_TMD), w); + + /* Additionally, trigger to-10 doorbell! */ + dt->dt_cond |= DTE_CI10DB; /* Set 10 doorbell */ + dte_pi(dt); + break; + + case DTECMD_EMP: /* Enter Secondary (monitor mode) ptcl */ + if (dt->dt_dv.dv_debug) + fprintf(dt->dt_dv.dv_dbf, "[DTECMD: enter sec]"); + dt->dt_secptcl = TRUE; + break; + + case DTECMD_EPP: /* Enter Primary ptcl */ + if (dt->dt_dv.dv_debug) + fprintf(dt->dt_dv.dv_dbf, "[DTECMD: enter prm]"); + if (!dte_prim(dt)) { /* Enter it, set up vars */ + fprintf(dt->dt_dv.dv_dbf, "[DTE: Can't enter prim ptcl]\r\n"); + break; + } + dt->dt_secptcl = FALSE; + dt->dt_dtmsent = FALSE; /* Pretend haven't sent date/time */ + dte_dbprmp(dt); /* Now do primary immediately! */ + if (!dt->dt_secptcl) { + /* If successfully entered prim ptcl, queue up ack-all stuff */ + /* (for now, skip the LPT and LPT1 allocs) */ + (void) dte_10qpkt(dt, + SWAB(DTE_QFN_ACK), /* Function 15 Ack All */ + SWAB(07), /* Random NZ device */ + SWAB(0), /* Random data */ + 0, 0, NULL); /* No other data */ + + /* If date/time not yet sent, queue a date/time packet now? + */ + if (!dt->dt_dtmsent) + dte_dtmsend(dt); /* Send date-time to 10 */ + } + break; /* If bombs, merely ignore it */ + + case DTECMD_RTM: /* Get date/time */ + if (dt->dt_dv.dv_debug) + fprintf(dt->dt_dv.dv_dbf, "[DTECMD: Get DTM]"); + { + /* Bits 4-19 of DTECMD are EPT offset to store a 3-word value, with + ** 2 right-justified 16-bit values in each word: + ** 0: + ** 1: + ** 2: <-unused-> + */ + /* Note T10 ignores DOW+DST info. */ + /* T20 treats DST info as: 0200 = DST flag bit + ** 0177 = Timezone + */ + + struct tm t; + register uint32 i; + register vmptr_t vp; + + /* Get high 16-bit field (B4-19) from DTECMD word */ + i = ((LHGET(w) << 2) | (RHGET(w) >> 16)) & MASK16; + if (i & (~0777)) { + fprintf(dt->dt_dv.dv_dbf, "[DTE: bad GDT offset %lo]", + (long)i); + break; /* Don't return date/time */ + + } + vp = vm_physmap(cpu.mr_ebraddr + i); + + if (!os_tmget(&t)) /* Get current date/time */ + break; /* Failed, return nothing */ + + LRHSET(w, 0, + ((1<<16) /* Set validity flag non-zero */ + | (t.tm_year + 1900) /* Make year full A.D. quantity */ + ) & H10MASK); + vm_pset(vp, w); + + i = t.tm_mday - 1; /* Get day-of-month (0-31) */ + LRHSET(w, + ((t.tm_mon<<6) + | (i >> 2)) & H10MASK, + ((i << 16) + | (((t.tm_wday+6) % 7)<<8) + | (t.tm_isdst ? 00377 : 0)) & H10MASK); + ++vp; + vm_pset(vp, w); + + /* Time is secs/2 to fit in a 16-bit word */ + i = (((((long)t.tm_hour * 60) + t.tm_min) * 60) + t.tm_sec) >> 1; + LRHSET(w, (i >> 2) & H10MASK, + (i << 16) & H10MASK); + ++vp; + vm_pset(vp, w); + + } + break; + + /* KLDCP commands */ + + case DTECMD_DCP_CTYO: /* KLDCP output char to CTY */ + if (dt->dt_dv.dv_debug) + fprintf(dt->dt_dv.dv_dbf, "[DTEO: %o]", (int)RHGET(w)&0377); + os_ttyout((int)RHGET(w)&0377); + break; + + case DTECMD_DCP_RDSW: /* Read data switches */ + vm_pset(vm_physmap(cpu.mr_ebraddr + DTEE_F11), cpu.mr_dsw); + break; + + case DTECMD_DCP_DDTIN: /* DDT input mode */ + { + register int ch; + if ((ch = os_ttyin()) < 0) + op10m_setz(w); /* No input, return 0 */ + else { + ch &= 0177; /* Mask for safety */ + os_ttyout(ch); /* Echo, sigh */ + LRHSET(w, 0, ch & 0377); + } + vm_pset(vm_physmap(cpu.mr_ebraddr + DTEE_F11), w); + } + break; + + case DTECMD_DCP_TIW: /* TTY input, wait */ + { + /* This one is dangerous as it actually halts the 10 + ** until some TTY input happens! But doing it right is painful + ** and only diagnostics should be using this anyway. + */ + register int ch, tmo = 180; + + if (DVDEBUG(dt)) + fprintf(DVDBF(dt), "[DTE KLDCP_TIW: %d sec...", tmo); + + for (;;) { + if ((ch = os_ttyin()) >= 0) { + ch &= 0177; /* Mask for safety */ + os_ttyout(ch); /* Echo, sigh */ + LRHSET(w, 0, ch & 0377); + if (DVDEBUG(dt)) + fprintf(DVDBF(dt), " %o]", ch); + break; + } + if (--tmo <= 0) { + op10m_setz(w); /* No input, return 0 */ + if (DVDEBUG(dt)) + fprintf(DVDBF(dt), " timeout]"); + break; + } + os_sleep(1); + } + vm_pset(vm_physmap(cpu.mr_ebraddr + DTEE_F11), w); + } + break; + + + case DTECMD_DCP_CLDI: /* Clear DDT input mode */ + break; + + case DTECMD_DCP_PGM: /* Program control; low byte subcmd */ + { + register int cmd = RHGET(w)&0377; /* Find subcmd */ + switch (cmd) { + case 01: + fprintf(DVDBF(dt), "[DTE KLDCP: Fatal pgm error in 10]\r\n"); + break; + case 02: + fprintf(DVDBF(dt), "[DTE KLDCP: Error halt in 10]\r\n"); + break; + case 03: + fprintf(DVDBF(dt), "[DTE KLDCP: End of program]\r\n"); + break; + case 04: + fprintf(DVDBF(dt), "[DTE KLDCP: End of pass]\r\n"); + break; + case 05: + /* Get clock default word. See dvdte.h for bits. */ + LRHSET(w,0,0); /* For now, just claim normal clk */ + if (DVDEBUG(dt)) + fprintf(DVDBF(dt), "[DTE KLDCP: Clk src %lo,,%lo]\r\n", + (long)LHGET(w), (long)RHGET(w)); + break; + default: + fprintf(DVDBF(dt), "[DTE KLDCP PGM(%o) unimplemented]", cmd); + } + } + break; + + case DTECMD_DCP_CLK: /* Clock control; low byte subcmd */ + { + register int cmd = RHGET(w)&0377; /* Find subcmd */ + uint32 ticks; + + switch (cmd) { + case DTECMD_DCPCLK_OFF: + if (DVDEBUG(dt)) + fprintf(DVDBF(dt), "[DTE KLDCP: Clock off]"); + dte_clkset(dt, 0); + break; + case DTECMD_DCPCLK_ON: + if (DVDEBUG(dt)) + fprintf(DVDBF(dt), "[DTE KLDCP: Clock on]"); + dte_clkset(dt, 1); /* Make clock enabled */ + break; + case DTECMD_DCPCLK_WAIT: + w = vm_pget(vm_physmap(cpu.mr_ebraddr + DTEE_T11)); + ticks = (LHGET(w)<<18) | RHGET(w); + if (DVDEBUG(dt)) + fprintf(DVDBF(dt), "[DTE KLDCP: Clock wait %ld.]", + (long)ticks); + dt->dt_clkcnt = ticks; + dte_clkset(dt, 2); /* Enable clock, do wait */ + break; + case DTECMD_DCPCLK_READ: + if (DVDEBUG(dt)) + fprintf(DVDBF(dt), "[DTE KLDCP: Clock read %ld.]", + (long) dt->dt_clkticks); + LRHSET(w, (dt->dt_clkticks >> 18) & H10MASK, + (dt->dt_clkticks & H10MASK)); + vm_pset(vm_physmap(cpu.mr_ebraddr + DTEE_F11), w); + break; + default: + fprintf(DVDBF(dt), "[DTE KLDCP CLK(%o) unimplemented]", cmd); + } + } + break; + } + + + /* After all commands (even bogus ones) set cmd-done flag -1 as + ** an ACK from the 11. This isn't needed for monitor-mode + ** TTY output under RSX20F (only KLDCP), but shouldn't hurt. + */ + op10m_seto(w); + vm_pset(vm_physmap(cpu.mr_ebraddr + DTEE_FLG), w); +} + +/* KLDCP clock facilities */ + +/* Turn clock on or off, setting new state. */ + +static void dte_clkset(register struct dte *dt, int on) +{ + dt->dt_clkf = on; /* Set new state now */ + + if (on) { + dt->dt_clkticks = 0; /* Reset count of ticks */ + if (dt->dt_clk) /* Activate timer */ + clk_tmractiv(dt->dt_clk); + else /* Gotta get it for 1st time */ + dt->dt_clk = clk_tmrget(dte_clktmo, (void *)dt, + CLK_USECS_PER_SEC/60); + } else { + if (dt->dt_clk) + clk_tmrquiet(dt->dt_clk); /* Make quiescent */ + } +} + +/* Handle clock timeout. */ + +static int dte_clktmo(void *arg) +{ + register struct dte *dt = (struct dte *)arg; + + dt->dt_clkticks++; /* Update tick count */ + switch (dt->dt_clkf) { + case 2: /* Clock-wait mode? */ + if (--(dt->dt_clkcnt) > 0) + break; /* If still waiting, do nothing */ + /* Wait timed out, drop thru to tickle 10 */ + + case 1: + /* KLDCP clock tick! Set loc 445 nonzero and send a TO10 DB */ + { + register w10_t w; + if (DVDEBUG(dt)) + fprintf(DVDBF(dt), "[DTE KLDCP clock timeout]"); + op10m_seto(w); + vm_pset(vm_physmap(cpu.mr_ebraddr + DTEE_CKF), w); + dt->dt_cond |= DTE_CI10DB; /* Set 10 doorbell */ + dte_pi(dt); + } + break; + } + return CLKEVH_RET_REPEAT; +} + +/* Do primary protocol stuff in response to doorbell +** EPT refs can use pre-computed & saved pointers/offsets for speed. +*/ +static void dte_dbprmp(register struct dte *dt) +{ + register w10_t w; + register int i, qct; + + /* First verify we actually have a comm area by checking DTE's examine + ** protection word, then fetching the comrgn status word to see if + ** the 10's "to-11" area is valid. + ** Real FE does this with just the attempted fetch, but I want to + ** avoid spurious error messages from dte_fetch(). + */ + if (op10m_skipe(vm_pget(dt->dt_eptcb + DTE_CBEPW)) + || (w = dte_fetch(dt, dt->dt_et11off + DTE_CMTW_STS), + !op10m_tlnn(w, DTE_CMT_TST))) { + + if (dt->dt_dv.dv_debug) + fprintf(dt->dt_dv.dv_dbf, "[DTE: Leaving prim ptcl: %lo,,%lo]\r\n", + (long)LHGET(w), (long)RHGET(w)); + + /* Ugh, must go into secondary ptcl. */ + if (!dt->dt_ismaster) + panic("[DTE: Non-master DTE %d trying to enter sec ptcl?]", + dt->dt_dten); + dt->dt_secptcl = TRUE; + return; /* Just return, let caller invoke dte_dosecp() */ + } + if (op10m_tlnn(w, (DTE_CMT_PWF|DTE_CMT_L11|DTE_CMT_INI))) { + fprintf(dt->dt_dv.dv_dbf, "[DTE: Unsupp sts bits %lo,,%lo]", + (long)LHGET(w), (long)RHGET(w)); + return; + } + + /* OK, so assume comm areas are all OK. Get info about queued + ** packet and gobble it up. + */ + if (op10m_trnn(w, DTE_CMT_IP)) { /* -10 saying this is indirect? */ + /* Verify indirect xfer in progress */ + if ( ! dt->dt_rcv_indir) { + panic("[DTE: -10 IIP but -11 not?]"); + } + dt->dt_rcvpkt.dtep_wdmod = /* Remember if full-word transfer */ + op10m_tlnn(w, DTE_CMT_FWD); + + /* Get count word */ + w = dte_fetch(dt, dt->dt_et11off + DTE_CMTW_CNT); + qct = RHGET(w) & DTE_CMT_QCT; /* Find # bytes this xfer */ + + /* Handle indirect xfer (finish off-- either byte or word mode) + ** Note this assumes + ** only ONE indirect is ever done, right after a msg header. + */ + if (qct >= sizeof(dt->dt_rcvpkt.dtep_data)) { + fprintf(dt->dt_dv.dv_dbf, + "[DTE: (RCV QCT %d.) > (max pkt size %d.)]", + qct, (int)sizeof(dt->dt_rcvpkt.dtep_data)); + qct = sizeof(dt->dt_rcvpkt.dtep_data); /* Truncate data */ + } + if (dt->dt_rcvpkt.dtep_wdmod) + dte_rdword(dt, qct, &dt->dt_rcvpkt.dtep_data[0]); + else + dte_rdbyte(dt, qct, &dt->dt_rcvpkt.dtep_data[0]); + dt->dt_rcvpkt.dtep_tcnt += qct; /* Update total bytes read */ + + dte_11xfrdon(dt); /* To-11 xfer done, wrap up */ + dte_xctfn(dt); /* Execute function for rcvd msg */ + dte_11done(dt); /* Wrap up */ + dt->dt_rcv_gothdr = FALSE; + dt->dt_rcv_indir = FALSE; + return; + } + if (dt->dt_rcv_indir) { + panic("[DTE: -11 IIP but -10 not?]"); + } + + + /* Handle direct header, always byte mode. + ** Check TO11QC field (# in queue of -10). + */ + i = (RHGET(w) & DTE_CMT_11IC); + if (i == dt->dt_rcv_11qc) { /* If no different from current val, */ + /* Just set TOIT bit and return */ + if (dt->dt_dv.dv_debug) + fprintf(dt->dt_dv.dv_dbf, "[DTE: 11QC %o unchanged, 11DB ignored]", + i); + + op10m_tro(dt->dt_snd_sts, DTE_CMT_TOT); /* Set TOIT bit in to-10 sts */ + dte_store(dt, dt->dt_dt10off + DTE_CMTW_STS, dt->dt_snd_sts); + return; + } else if (i != ((dt->dt_rcv_11qc + 1) & 0377)) { + /* Error, queue count mismatch. + ** Note the 0377 mask in the above test, to handle wraparound. + */ + fprintf(dt->dt_dv.dv_dbf, "[DTE: (RCV 11QC %o) != (11's 11QC %o+1)]", + i, dt->dt_rcv_11qc); + /* Recover by just continuing, to set new QC */ + } + + /* New header ready to slurp up! */ + dt->dt_rcv_11qc = i; /* Set our new msg count value */ + + /* + ** CMPCT has the total # bytes in the protocol message. + ** CMQCT has the # bytes to fetch in this hardware xfer. + ** If indirect, more bytes follow in next wakeup, will have to set + ** up state to remember what we got. + ** No single packet will be larger than 254.+10. bytes -- will be broken + ** up if so. + */ + w = dte_fetch(dt, dt->dt_et11off + DTE_CMTW_CNT); + qct = RHGET(w) & DTE_CMT_QCT; /* Find # bytes this xfer */ + op10m_rshift(w, 16); /* Get DTE_CMT_PCT field */ + i = RHGET(w) & MASK16; /* Get CMPCT */ + +#define MAXXFER ((int)(sizeof(dt->dt_rcvpkt.dtep_data)+DTE_QMH_SIZ)) +#if 0 /* Commented out - T10 apparently hits this constantly */ + if (i < qct) { + fprintf(dt->dt_dv.dv_dbf, "[DTE: To-11 PCT:%d. < QCT:%d.]", i, qct); + } +#endif + if (i > MAXXFER) { + fprintf(dt->dt_dv.dv_dbf, "[DTE: (RCV PCT %d.) > (max pkt size %d.)]", + i, MAXXFER); + /* Truncate data? Let actual xfer handle it. */ + } + if (qct > MAXXFER) { + fprintf(dt->dt_dv.dv_dbf, "[DTE: (RCV QCT %d.) > (max pkt size %d.)]", + qct, MAXXFER); + qct = MAXXFER; /* Truncate data */ + } +#undef MAXXFER + + /* Now read header directly into our internal structure. */ + if (qct < DTE_QMH_SIZ) { + fprintf(dt->dt_dv.dv_dbf, "[DTE: To-11 QCT:%d. < Header]", qct); + dte_11xfrdon(dt); /* Punt, tell 10 xfer is done */ + return; + } + + dte_rdhd(dt); /* Read 10 bytes from current to-11 BP */ + dt->dt_rcv_gothdr = TRUE; + dt->dt_rcv_indir = FALSE; + + /* Check to see if more data follows or not */ + if (dt->dt_rcvpkt.dtep_fn & DTE_QMH_INDIRF) { + /* Indirect for rest of data... so set up and return. + ** Note that count field of msg header is ignored! + */ + if (dt->dt_dv.dv_debug) + fprintf(dt->dt_dv.dv_dbf, "[DTE: @ fn: %o]",dt->dt_rcvpkt.dtep_fn); + dt->dt_rcv_indir = TRUE; /* Remember doing indirect */ + dte_11xfrdon(dt); /* Give 10 an xfer-done PI */ + return; /* That's all -- give 10 its turn */ + } + + /* Not indirect -- fully direct message. Check msg count word to see + ** if it has more data beyond the ten header bytes we read. Actually, + ** we ignore it except for doing an error check, and pay attention + ** mostly to CMQCT. We already know that CMQCT is small enough for + ** a complete direct transfer. + */ + if (dt->dt_rcvpkt.dtep_cnt != qct) { + fprintf(dt->dt_dv.dv_dbf, "[DTE: To-11 direct QCT:%d. != Hdr:%d.]", + qct, dt->dt_rcvpkt.dtep_cnt); + } + + if ((qct -= DTE_QMH_SIZ) > 0) { /* More data? */ + /* Yep, gobble up additional data in byte mode. */ + + if (dt->dt_dv.dv_debug) + fprintf(dt->dt_dv.dv_dbf, "[DTE: R %d, fn=%o]", + qct, dt->dt_rcvpkt.dtep_fn); + + dte_rdbyte(dt, qct, &dt->dt_rcvpkt.dtep_data[0]); + dt->dt_rcvpkt.dtep_tcnt += qct; /* Update total bytes read */ + } + + /* Direct xfer all done, clear TOIT bit and tickle PI */ + dte_11xfrdon(dt); /* Give 10 an xfer-done PI */ + + /* Execute function now, or via 11done? */ + dte_xctfn(dt); + + /* Now carry out functions as if 11-done interrupt happened! */ + dte_11done(dt); + + dt->dt_rcv_gothdr = FALSE; + dt->dt_rcv_indir = FALSE; +} + +static void dte_11xfrdon(register struct dte *dt) +{ + /* Receiver (11) clears TOIT status bit in area used for sending to 10 + */ + op10m_trz(dt->dt_snd_sts, DTE_CMT_TOT); /* Clr TOIT bit in to-10 sts */ + dte_store(dt, dt->dt_dt10off + DTE_CMTW_STS, dt->dt_snd_sts); + + dt->dt_cond |= DTE_CI11DN; /* Say To-11 transfer done */ + + /* PI will be triggered by CONO code just before it returns */ +} + +/* + When gets To-11 Done Interrupt (always, whenever DTE to-11 + xfer counts out): + + Clears to-11 Done flag in DTE (DON11C) + Gets back saved byte count from start of xfer (TO11BS) + (Note was word count if not byte mode), uses that + to verify ending address of xfer. + Crashes if doesn't match predicted end of xfer. + Checks to see whether reading header (start of packet) or + is doing part of an indirect. + + If start of packet: + Swaps bytes of function, count, and device words to + get them into PDP-11 order. + Checks device and function for valid ranges. Only + checks low byte of function word. If + invalid, crashes. + Checks high bit of function wd to see if indirect + data follows. If so: + Clears TOIT bit in 10's to-11 status, and + that's all. This appears to rely on the + 10 getting a xfer-done interrupt (ie I-bit + set by 11?) so it can set up the indirect + xfer and ring the 11's doorbell again. + Anyway, returns. + If not (ie fully direct), checks message's count + word to see if any more data beyond the 10 header + bytes. + If no more data, + Assumes xfer done, queues the msg, + clears TOIT bit, returns. + If more data, + Starts new xfer with new count; sets + I-bit so 10 gets interrupted when xfer done. + Updates EQSZ (internal CMQCT value) tho not + sure if this does us any good. + Returns. + + + Else, doing indirect: + Assume packet all done, queues the msg, clears + TOIT bit, returns. + +*/ +static void dte_11done(register struct dte *dt) +{ + + /* Transform data into header */ + + +} + +/* Read packet/message header (10 bytes) +** Note BPs must be interpreted as being in executive virtual space, +** which isn't necessarily the same as physical. +** However, this code cheats by assuming data doesn't cross a page +** boundary. +*/ +static void dte_rdhd(register struct dte *dt) +{ + register w10_t bp, w; + register vmptr_t abp, vp; + register vaddr_t va; + register unsigned int i; + + /* For now, assume DTE's starting BP is 8-bit OWLBP at beg of word. + ** Verify this before continuing. + */ + abp = dt->dt_eptcb + DTE_CBOBP; /* Get BP addr */ + bp = vm_pget(abp); + if (LHGET(bp) != 0441000) { + fprintf(dt->dt_dv.dv_dbf, "[DTE: Bad to-11 BP %lo,,%lo]", + (long)LHGET(bp), (long)RHGET(bp)); + return; /* For now, punt entirely */ + } + + /* Normal fast case. */ + va_gmake(va, 0, RHGET(bp)); /* Make exec virt addr, sect 0 */ + vp = vm_execmap(va, VMF_READ|VMF_NOTRAP); /* Map it, don't trap */ + if (!vp) { + panic("DTE: Page fail reading R hdr at %lo", (long)RHGET(bp)); + /* Later do real page-fail with special device data? + ** See T20 APRSRV for details of what it expects. + */ + } + + w = vm_pget(vp); /* Get 1st word */ + + dt->dt_rcvpkt.dtep_tcnt = DTE_QMH_SIZ; /* 10 bytes read so far */ + + dt->dt_rcvpkt.dtep_cnt = (LHGET(w)>>2); /* Get 16-bit count */ + dt->dt_rcvpkt.dtep_fn = /* Get 16-bit function */ + ((LHGET(w)&03)<<14) | (RHGET(w)>>4); + + /* Get 16-bit device from next 10 word */ + dt->dt_rcvpkt.dtep_dev = (vm_pgetlh(vp+1)>>2); + + /* Get first 2 data bytes from high 16 bits of next 10 word after that */ + dt->dt_rcvpkt.dtep_db1 = (i = (vm_pgetlh(vp+2)>>2)) & 0377; + dt->dt_rcvpkt.dtep_db0 = (i >> 8) & 0377; + + dt->dt_rcvpkt.dtep_wdmod = FALSE; /* Assume byte mode */ + + /* Now update BP as if read 10 bytes */ + op10m_addi(bp, 2); + LHSET(bp, 0241000); + vm_pset(abp, bp); /* Store back in EPT */ +} + + +static h10_t bp8lhtab[5] = { + 0041000, + 0141000, + 0241000, + 0341000, + 0441000 +}; +static h10_t bp9lhtab[5] = { + 0001100, + 0111100, + 0221100, + 0331100, + 0441100 +}; + +/* Read packet/message byte data +** This also cheats by ignoring page boundaries once a physical memory +** address is obtained. +*/ +static void dte_rdbyte(register struct dte *dt, register int cnt, register unsigned char *ucp) +{ + register w10_t bp, w; + register vmptr_t abp, vp; + register int i; + register vaddr_t va; + + /* For now, assume DTE's starting BP is 8-bit OWLBP. + ** Verify this before continuing. + */ + abp = dt->dt_eptcb + DTE_CBOBP; /* Get BP addr */ + bp = vm_pget(abp); + + /* Normal fast case. */ + va_gmake(va, 0, RHGET(bp)); /* Make exec virt addr, sect 0 */ + vp = vm_execmap(va, VMF_READ|VMF_NOTRAP); /* Map it, don't trap */ + if (!vp) { + panic("DTE: Page fail reading R data at %lo", (long)RHGET(bp)); + } + + i = LHGET(bp) >> 15; /* Use high octal digit as index */ + if (i > 0) /* Set up first word */ + w = vm_pget(vp); + + if (i <= 4 && (bp8lhtab[i] == LHGET(bp))) { + /* Do 8-bit byte loop */ + while (--cnt >= 0) { + if (--i < 0) { /* Bump to next word? */ + op10m_inc(bp); + ++vp; + w = vm_pget(vp); + i = 3; + } + switch (i) { + case 0: *ucp++ = (RHGET(w) >> 4) & 0377; break; + case 1: *ucp++ = ((LHGET(w) & 03)<<6) | (RHGET(w) >> 12); break; + case 2: *ucp++ = (LHGET(w) >> 2) & 0377; break; + case 3: *ucp++ = (LHGET(w) >> 10) & 0377; break; + } + } + /* Now update BP as if read cnt bytes */ + LHSET(bp, bp8lhtab[i]); /* Set up appropriate LH */ + + } else if (i <= 4 && (bp9lhtab[i] == LHGET(bp))) { + /* Do 9-bit byte loop */ + while (--cnt >= 0) { + if (--i < 0) { /* Bump to next word? */ + op10m_inc(bp); + ++vp; + w = vm_pget(vp); + i = 3; + } + switch (i) { + case 0: *ucp++ = RHGET(w) & 0377; break; + case 1: *ucp++ = (RHGET(w) >> 9) & 0377; break; + case 2: *ucp++ = LHGET(w) & 0377; break; + case 3: *ucp++ = (LHGET(w) >> 9) & 0377; break; + } + } + /* Now update BP as if read cnt bytes */ + LHSET(bp, bp9lhtab[i]); /* Set up appropriate LH */ + + } else { + fprintf(dt->dt_dv.dv_dbf, "[DTE: Bad to-11 BP %lo,,%lo]", + (long)LHGET(bp), (long)RHGET(bp)); + return; /* For now, punt entirely */ + } + + vm_pset(abp, bp); /* Store BP back in EPT */ +} + + +/* Read packet/message word data +** This also cheats by ignoring page boundaries once a physical memory +** address is obtained. +*/ +static h10_t bp16lhtab[5] = { + 0042000, + 0242000, + 0442000 +}; +static void dte_rdword(register struct dte *dt, + register int cnt, /* Note: Byte count! */ + register unsigned char *ucp) +{ + register w10_t bp, w; + register vmptr_t abp, vp; + register int i; + register vaddr_t va; + + /* For now, assume DTE's starting BP is 16-bit OWLBP. + ** Verify this before continuing. + */ + abp = dt->dt_eptcb + DTE_CBOBP; /* Get BP addr */ + bp = vm_pget(abp); + i = LHGET(bp) >> 16; /* Use high 2 bits as index */ + if (i > 3 || (bp16lhtab[i] != LHGET(bp))) { + fprintf(dt->dt_dv.dv_dbf, "[DTE: Bad to-11 BP %lo,,%lo]", + (long)LHGET(bp), (long)RHGET(bp)); + return; /* For now, punt entirely */ + } + + /* Normal fast case. */ + va_gmake(va, 0, RHGET(bp)); /* Make exec virt addr, sect 0 */ + vp = vm_execmap(va, VMF_READ|VMF_NOTRAP); /* Map it, don't trap */ + if (!vp) { + panic("DTE: Page fail reading R indir at %lo", (long)RHGET(bp)); + } + if (i > 0) /* Set up first word */ + w = vm_pget(vp); + + cnt >>= 1; /* Make count be a word count, round down */ + while (--cnt >= 0) { + if (--i < 0) { /* Bump to next word? */ + op10m_inc(bp); + ++vp; + w = vm_pget(vp); + i = 1; + } + switch (i) { + case 0: /* Low 16-bit word */ + *ucp++ = ((LHGET(w) & 03)<<6) | (RHGET(w) >> 12); + *ucp++ = (RHGET(w) >> 4) & 0377; + break; + case 1: + *ucp++ = (LHGET(w) >> 10) & 0377; + *ucp++ = (LHGET(w) >> 2) & 0377; + break; + } + } + + /* Now update BP as if read cnt bytes */ + LHSET(bp, bp16lhtab[i]); /* Set up appropriate LH */ + vm_pset(abp, bp); /* Store back in EPT */ +} + + +static void dte_xctfn(register struct dte *dt) +{ + register int i; + register unsigned char *cp; + + if (dt->dt_dv.dv_debug) { + fputs("[DTE: R msg: ", dt->dt_dv.dv_dbf); + dte_showpkt(dt, &dt->dt_rcvpkt); + fputs("]\r\n", dt->dt_dv.dv_dbf); + } + + switch (dt->dt_rcvpkt.dtep_fn & 0377) { + + case DTE_QFN_LCI: /* 01 LINE COUNT IS */ + /* This message is sent by the 10 upon startup init. + ** 11 must respond with a From-11 #2 (DTE_QFN_ALS) message, possibly + ** other messages later. (eg date/time) + */ + dte_10qpkt(dt, + SWAB(DTE_QFN_ALS), /* Function 02 CTY Alias is */ + SWAB(DTE_QDV_CTY), /* CTY dev */ + SWAB(DTE_DLS_CTY), /* CTY DLS Line */ + 0, 0, NULL); /* No other data */ + + /* Send output buffer allocation */ + if (dt->dt_ctyobs > 0) { /* Optional, can turn off */ + dte_10qpkt(dt, + SWAB(DTE_QFN_HLA), /* Function 023 Here is Line Alloc */ + SWAB(DTE_QDV_CTY), /* CTY dev */ + SWAB((DTE_DLS_CTY<<8) | dt->dt_ctyobs), /* Line 0, alloc */ + 0, 0, NULL); /* No other data */ + } + + dte_dtmsend(dt); /* Also send date/time */ + return; + +#if 0 /* to-11 Unsupported by 11 FE */ + case DTE_QFN_ALS: /* 02 CTY device alias */ +#endif + + case DTE_QFN_HSD: /* 03 HERE IS STRING DATA */ + if (dt->dt_rcvpkt.dtep_db0 == DTE_DLS_CTY) { /* If line 0 (CTY) */ + + case DTE_QFN_STA: /* 014 SEND TO ALL (SENT TO 11 ONLY) */ + /* For now, same as string data to CTY */ + if ((i = dt->dt_rcvpkt.dtep_tcnt - DTE_QMH_SIZ) > 0) + if (i > dt->dt_rcvpkt.dtep_db1) + i = dt->dt_rcvpkt.dtep_db1; /* # bytes in string */ +#if 1 /* New string regime */ + dte_ctysout(dt, dt->dt_rcvpkt.dtep_data, i); + dte_ctyforce(dt); +#else + for (cp = dt->dt_rcvpkt.dtep_data; --i >= 0;) + dte_ctyout(dt, *cp++); +#endif + dte_ctyack(dt); /* Temp hack - always ACK for now */ + } + return; + + case DTE_QFN_HLC: /* 04 HERE ARE LINE CHARACTERS */ + /* If 1st pair is line 0 (CTY), then output data char */ + if (dt->dt_rcvpkt.dtep_db0 == DTE_DLS_CTY) + dte_ctyout(dt, dt->dt_rcvpkt.dtep_db1); /* Yep, do it! */ + + /* Same for all additional pairs */ + if ((i = dt->dt_rcvpkt.dtep_tcnt - DTE_QMH_SIZ) > 0) { + for (cp = dt->dt_rcvpkt.dtep_data; i > 0; i -= 2, cp += 2) + if (*cp == DTE_DLS_CTY) + dte_ctyout(dt, cp[1]); + } +#if 1 + dte_ctyforce(dt); /* Force out any buffered CTY output */ +#endif + dte_ctyack(dt); /* Temp hack - always ACK for now */ + return; + + case DTE_QFN_RDS: /* 05 REQUEST DEVICE STATUS */ + case DTE_QFN_SDO: /* 06 SPECIAL DEVICE OPERATION */ + case DTE_QFN_STS: /* 07 HERE IS DEVICE STATUS */ +#if 0 /* Unsupported by 11 FE */ + case DTE_QFN_ESD: /* 010 ERROR ON DEVICE */ +#endif + break; + + case DTE_QFN_RTD: /* 011 REQUEST TIME OF DAY */ + dte_dtmsend(dt); /* No data, just respond with date/time */ + return; + + case DTE_QFN_HTD: /* 012 HERE IS TIME OF DAY */ + case DTE_QFN_FDO: /* 013 FLUSH OUTPUT (SENT TO 11 ONLY) */ + break; + /* 014 Handled up near 03 */ + case DTE_QFN_LDU: /* 015 A LINE DIALED UP (FROM 11 ONLY) */ + case DTE_QFN_LHU: /* 016 A LINE HUNG UP OR LOGGED OUT */ + case DTE_QFN_LBE: /* 017 LINE BUFFER BECAME EMPTY */ + case DTE_QFN_XOF: /* 020 XOF COMMAND TO THE FE */ + case DTE_QFN_XON: /* 021 XON COMMAND TO THE FE */ + case DTE_QFN_SPD: /* 022 SET TTY LINE SPEED */ + case DTE_QFN_HLA: /* 023 HERE IS LINE ALLOCATION */ +#if 0 /* Unsupported by 11 FE */ + case DTE_QFN_HRW: /* 024 HERE IS -11 RELOAD WORD */ +#endif + case DTE_QFN_ACK: /* 025 GO ACK ALL DEVICES AND UNITS */ + case DTE_QFN_TOL: /* 026 TURN OFF/ON LINE (0 off, 1 on) */ + case DTE_QFN_EDR: /* 027 ENABLE/DISABLE DATASETS */ + case DTE_QFN_LTR: /* 030 LOAD TRANSLATION RAM */ + case DTE_QFN_LVF: /* 031 LOAD VFU */ + case DTE_QFN_MSG: /* 032 SUPPRESS SYSTEM MESSAGES TO TTY */ + case DTE_QFN_KLS: /* 033 SEND KLINIK DATA TO THE -11 */ + case DTE_QFN_XEN: /* 034 ENABLE XON (SENT TO 11 ONLY) */ + case DTE_QFN_BKW: /* 035 BREAK-THROUGH WRITE */ + case DTE_QFN_DBN: /* 036 DEBUG MODE ON */ + case DTE_QFN_DBF: /* 037 DEBUG MODE OFF */ + case DTE_QFN_SNO: /* 040 IGNORE NEW CODE FOR SERIAL NUMBERS */ + break; + default: + fprintf(dt->dt_dv.dv_dbf, "[DTE: Unknown R fn: %o]\r\n", + dt->dt_rcvpkt.dtep_fn); + return; /* Do nothing after complaining */ + } + + /* If get here, function is known but can't be xctd. */ + if (dt->dt_dv.dv_debug) { + /* Packet already shown, don't show it again */ + fprintf(dt->dt_dv.dv_dbf, "[DTE: Unimpl R fn: %o]\r\n", + dt->dt_rcvpkt.dtep_fn); + } else if (dt->dt_dowarn) { + fputs("[DTE: Unimpl R fn: ", dt->dt_dv.dv_dbf); + dte_showpkt(dt, &dt->dt_rcvpkt); + fputs("]\r\n", dt->dt_dv.dv_dbf); + } +} + +static void dte_showpkt(register struct dte *dt, + register struct dtepkt_s *dtp) +{ + register FILE *f = dt->dt_dv.dv_dbf; + register int i; + register unsigned char *cp; + + if ((i = dtp->dtep_tcnt) != dtp->dtep_cnt) + fprintf(f, "tot=%d. ", i); + fprintf(f, "cnt=%d. fn=%o dev=%o d0=%o d1=%o", + dtp->dtep_cnt, dtp->dtep_fn, dtp->dtep_dev, + dtp->dtep_db0, dtp->dtep_db1); + if ((i -= DTE_QMH_SIZ) > 0) { + if (dtp->dtep_wdmod) + fputs(" WDMOD", f); + if (i > sizeof(dtp->dtep_data)) + i = sizeof(dtp->dtep_data); /* Truncate too-large count */ + cp = &dtp->dtep_data[0]; + while (--i >= 0) + fprintf(f, " %3o", *cp++); + } +} + +/* Stuff for sending TO the 10. +** Must maintain a small queue, sigh. +*/ + +/* General procedure: + +When 11 wants to send something to 10: + Modifies its to-10 region: + QSIZE set to 0,0,<# bytes in pkt>. + STS has its TO10IC byte incremented. (FE TO11QC, misnomer) + Sets up DTE from its side for data xfer (byte mode, buffer addr) + Triggers To-10 doorbell! + +10 handles doorbell interrupt: + Notices TO10IC has incremented by one (if not, no xfer started). + Sets its copy of TO10IC to be same (stored in 10's to-11 region for + this 11) + Sets its CMTOT bit (likewise in 10's to-11 status). + Clears doorbell + Gets CMQCT from 11's to-10 region (what 11 just set up) + Sets up DTE byte ptr. + Does DATAO with right count (# bytes, or # words). + Adds "I" bit if can read all of CMQCT into buffer, else + leaves off (fragmented read). + Returns. + + + +10-done interrupt always given to 10. +11-done interrupt only given to 11 if "I" bit was set. + +*/ + +/* Called when 10 gives DATAO to initiate transfer. +*/ +static void dte_10xfrbeg(register struct dte *dt) +{ + register struct dteq_s *q; + register int i; + + /* See if anything ready to send */ + if (!(q = dt->dt_sndq)) + return; /* Nope, just return silently */ + + /* Yep, send it! */ + if (dt->dt_dv.dv_debug) + fprintf(dt->dt_dv.dv_dbf, "[dte_10xfrbeg: sta=%d %o %o %o %d. %d.]", + dt->dt_snd_state, + q->q_sfn, q->q_sdev, q->q_swd1, q->q_bbcnt, q->q_wbcnt); + + + /* Determine our state. We could be: + ** - Starting header xfer + ** - in middle of header/data xfer + ** - Starting indirect data xfer + ** - in middle of indirect data xfer + */ + + /* For now, assume always have enough room to send at least the 10 header + ** bytes, so no intermediate states will exist for it. + */ + + switch (dt->dt_snd_state) { + case DTSND_HDR: + if (dt->dt_snd_cnt < DTE_QMH_SIZ) { + fprintf(dt->dt_dv.dv_dbf, "[dte_10xfrbeg: bad S cnt in DATAO: %d]", + dt->dt_snd_cnt); + return; + } + /* Write header, updates BP */ + dte_wrhd(dt, q); + + /* Update count, determine new state */ + if (q->q_bbcnt || q->q_wbcnt) /* More data to send? */ + dt->dt_snd_state = /* Yes, see if indirect */ + (q->q_sfn & SFN_INDBIT) ? DTSND_IND : DTSND_DAT; + dt->dt_snd_cnt -= DTE_QMH_SIZ; + if (dt->dt_snd_cnt <= 0 || dt->dt_snd_state != DTSND_DAT) + break; + /* Drop through to continue xfer with direct byte data */ + + case DTSND_DAT: + if (!(i = q->q_bbcnt)) { + fprintf(dt->dt_dv.dv_dbf, "[dte_10xfrbeg: No bcnt??]"); + break; + } + if (i > dt->dt_snd_cnt) + i = dt->dt_snd_cnt; /* Partial xfer */ + dte_wrbyte(dt, q, i); + dt->dt_snd_cnt -= i; + break; + + case DTSND_IND: + if (!(i = q->q_wbcnt)) { + fprintf(dt->dt_dv.dv_dbf, "[dte_10xfrbeg: No wbcnt??]"); + break; + } + i >>= 1; /* Make byte cnt into word cnt */ + if (i > dt->dt_snd_cnt) + i = dt->dt_snd_cnt; /* Partial xfer */ + dte_wrword(dt, q, i); + dt->dt_snd_cnt -= i; + break; + + default: + panic("[dte_10xfrbeg: bad state %o", dt->dt_snd_state); + } + + if (dt->dt_snd_cnt > 0) + fprintf(dt->dt_dv.dv_dbf, "[dte_10xfrbeg: 10cnt left: %d]", + dt->dt_snd_cnt); + + /* Transmission done, always give 10 an interrupt. */ + dt->dt_cond |= DTE_CI10DN; /* Set 10-Done bit */ + + /* If I-bit set, "interrupt" 11 as well. This basically tells the + ** 11 that this packet xfer is done, set up for next one. Here + ** it is interpreted to simply take current packet off queue and + ** line up the next one. + */ + if (dt->dt_snd_ibit) { + + /* If packet all done, take it off queue now */ + if (!(dt->dt_sndq = q->q_next)) /* Remove from queue */ + dt->dt_sndqtail = NULL; /* If became empty, fix up tail */ + q->q_next = dteqfreep; /* Add node to head of freelist */ + dteqfreep = q; + + /* Now should we rig up com area and push to-10 doorbell again, + ** or wait for 10 to handle its PI? Damn the torpedoes... + */ + if (dt->dt_sndq) + dte_10start(dt); + } else { + /* Packet not all done. Make sure we have more stuff, just to + ** double-check. + */ + if (!q->q_bbcnt && !q->q_wbcnt) + fprintf(dt->dt_dv.dv_dbf, "[dte_10xfrbeg: out of data, no I bit]"); + + /* If more stuff to be done, what else needs to be set up? + ** Should dte_10start() be invoked to set up com reg differently?? + ** YES!!!!!! + */ + + /* ## MUST WRITE CODE HERE IF SENDING INDIRECT ## */ + } + + dte_pi(dt); /* Always attempt PI for 10 */ +} + +/* Write to-10 message header in 10's memory. +** This routine cheats in a number of ways: +** (1) Assumes header is all in contiguous phys memory +** (2) Assumes deposit BP is 8-bit OWLBP to start of wd. +** (3) Clears unused low bits of 3 words (4, 4, 20). +*/ +static void dte_wrhd(register struct dte *dt, register struct dteq_s *q) +{ + register w10_t bp, w; + register vmptr_t abp, vp; + register vaddr_t va; + register unsigned int i; + + /* For now, assume DTE's starting BP is 8-bit OWLBP at beg of word. + ** Verify this before continuing. + */ + abp = dt->dt_eptcb + DTE_CBIBP; /* Get BP addr */ + bp = vm_pget(abp); + if (LHGET(bp) != 0441000) { + fprintf(dt->dt_dv.dv_dbf, "[DTE: Bad to-10 BP %lo,,%lo]", + (long)LHGET(bp), (long)RHGET(bp)); + return; /* For now, punt entirely */ + } + + if (dt->dt_dv.dv_debug) + fprintf(dt->dt_dv.dv_dbf, "[dte_wrhd: %o %o %o %d. %d. -> %lo]", + q->q_sfn, q->q_sdev, q->q_swd1, q->q_bbcnt, q->q_wbcnt, + (long)RHGET(bp)); + + /* Normal fast case. */ + va_gmake(va, 0, RHGET(bp)); /* Make exec virt addr, sect 0 */ + vp = vm_execmap(va, VMF_WRITE|VMF_NOTRAP); /* Map it, don't trap */ + if (!vp) { + panic("DTE: Page fail writing to-10 hdr at %lo", (long)RHGET(bp)); + } + + /* Set 1st word. Note @ bit in function already set up. */ + i = DTE_QMH_SIZ + q->q_bbcnt + q->q_wbcnt; + i = SWAB(i); /* Get total byte count */ + LRHSET(w, ((h10_t)i << 2) | (((q->q_sfn)>>14)&03), + (((h10_t)q->q_sfn << 4) & H10MASK)); + vm_pset(vp, w); /* Set 1st word */ + + LRHSET(w, ((h10_t)q->q_sdev<<2), 0); + vm_pset(vp+1, w); /* Set 2nd word */ + + LRHSET(w, ((h10_t)q->q_swd1<<2), 0); + vm_pset(vp+2, w); /* Set 3rd word */ + + /* Now update BP as if written 10 bytes */ + op10m_addi(bp, 2); + LHSET(bp, 0241000); + vm_pset(abp, bp); /* Store back in EPT */ +} + +/* Write packet/message byte data +** This also cheats by ignoring page boundaries once a physical memory +** address is obtained. +*/ +static void dte_wrbyte(register struct dte *dt, register struct dteq_s *q, register int cnt) +{ + register w10_t bp, w; + register vmptr_t abp, vp; + register int i; + register vaddr_t va; + register uint18 uhw; + register unsigned char *ucp; + + /* For now, assume DTE's starting BP is 8-bit OWLBP. + ** Verify this before continuing. + */ + abp = dt->dt_eptcb + DTE_CBIBP; /* Get BP addr */ + bp = vm_pget(abp); + i = LHGET(bp) >> 15; /* Use high octal digit as index */ + if (i > 4 || (bp8lhtab[i] != LHGET(bp))) { + fprintf(dt->dt_dv.dv_dbf, "[DTE: Bad to-10 BP %lo,,%lo]", + (long)LHGET(bp), (long)RHGET(bp)); + return; /* For now, punt entirely */ + } + + /* Normal fast case. */ + va_gmake(va, 0, RHGET(bp)); /* Make exec virt addr, sect 0 */ + vp = vm_execmap(va, VMF_WRITE|VMF_NOTRAP); /* Map it, don't trap */ + if (!vp) { + panic("DTE: Page fail writing S hdr at %lo", (long)RHGET(bp)); + } + + if (!(ucp = q->q_dcp)) { + panic("dte_wrbyte: no data ptr"); + } + q->q_bbcnt -= cnt; /* Pre-update count of data */ + + w = vm_pget(vp); /* Set up first word */ + + while (--cnt >= 0) { + if (--i < 0) { /* Bump to next word? */ + op10m_inc(bp); + vm_pset(vp, w); /* Store word we're done with */ + ++vp; + w = vm_pget(vp); + i = 3; + } + uhw = *ucp++ & 0377; +#define UHBYTE ((uint18)0377) + switch (i) { + case 0: RHSET(w, (RHGET(w) & ~(UHBYTE<<4)) | (uhw<<4)); + break; + case 1: LRHSET(w, (LHGET(w) & ~03) | ((uhw >> 6) & 03), + (RHGET(w) & 07777) | ((uhw & 077)<<(8+4))); + break; + case 2: LHSET(w, (LHGET(w) & ~(UHBYTE<<2)) | (uhw << 2)); + break; + case 3: LHSET(w, (LHGET(w) & ~(UHBYTE<<(8+2))) | (uhw << (8+2))); + break; +#undef UHBYTE + } + } + q->q_dcp = ucp; /* Update queue pkt data ptr */ + vm_pset(vp, w); /* Store word we're done with */ + + /* Now update BP as if written cnt bytes */ + LHSET(bp, bp8lhtab[i]); /* Set up appropriate LH */ + vm_pset(abp, bp); /* Store back in EPT */ +} + +static void dte_wrword(register struct dte *dt, + register struct dteq_s *q, + register int wcnt) /* Note: word count! */ +{ + register w10_t bp, w; + register vmptr_t abp, vp; + register int i; + register vaddr_t va; + + /* For now, assume DTE's starting BP is 16-bit OWLBP. + ** Verify this before continuing. + */ + abp = dt->dt_eptcb + DTE_CBIBP; /* Get BP addr */ + bp = vm_pget(abp); + i = LHGET(bp) >> 16; /* Use high 2 bits as index */ + if (i > 3 || (bp16lhtab[i] != LHGET(bp))) { + fprintf(dt->dt_dv.dv_dbf, "[DTE: Bad to-10 BP %lo,,%lo]", + (long)LHGET(bp), (long)RHGET(bp)); + return; /* For now, punt entirely */ + } + + /* Normal fast case. */ + va_gmake(va, 0, RHGET(bp)); /* Make exec virt addr, sect 0 */ + vp = vm_execmap(va, VMF_WRITE|VMF_NOTRAP); /* Map it, don't trap */ + if (!vp) { + panic("DTE: Page fail writing S indir at %lo", (long)RHGET(bp)); + } + + if (i > 0) /* Set up first word */ + w = vm_pget(vp); +#if 0 + cnt >>= 1; /* Make count be a word count, round down */ + while (--cnt >= 0) { + if (--i < 0) { /* Bump to next word? */ + op10m_inc(bp); + ++vp; + w = vm_pget(vp); + i = 1; + } + switch (i) { + case 0: /* Low 16-bit word */ + *ucp++ = ((LHGET(w) & 03)<<6) | (RHGET(w) >> 12); + *ucp++ = (RHGET(w) >> 4) & 0377; + break; + case 1: + *ucp++ = (LHGET(w) >> 10) & 0377; + *ucp++ = (LHGET(w) >> 2) & 0377; + break; + } + } +#else + panic("dte_wrword: not implemented yet"); +#endif + + /* Now update BP as if read cnt bytes */ + LHSET(bp, bp16lhtab[i]); /* Set up appropriate LH */ + vm_pset(abp, bp); /* Store back in EPT */ +} + +/* Code for 11 side to queue up packets for 10. +*/ + + +/* Set up and enqueue a packet +*/ +static int dte_10qpkt(register struct dte *dt, + unsigned int sfn, + unsigned int sdev, + unsigned int swd1, + int bbcnt, + int wbcnt, + unsigned char *dcp) +{ + register struct dteq_s *q; + + if (dt->dt_dv.dv_debug) + fprintf(dt->dt_dv.dv_dbf, "[dte_10qpkt: %o %o %o %d. %d.]", + sfn, sdev, swd1, bbcnt, wbcnt); + + /* First get a free queue element */ + if (!(q = dteqfreep)) { + fprintf(dt->dt_dv.dv_dbf, "[DTE: no free QPs!]"); + return FALSE; + } + dteqfreep = q->q_next; /* Pluck off freelist */ + + /* Fill it out */ + q->q_next = NULL; + q->q_sfn = sfn; + q->q_sdev = sdev; + q->q_swd1 = swd1; + q->q_bbcnt = bbcnt; + if (q->q_wbcnt = (bbcnt ? 0 : wbcnt)) /* If sending words, */ + q->q_sfn |= SFN_INDBIT; /* Force msg to use indirect data */ + if (bbcnt || wbcnt) + q->q_dcp = dcp; + + /* Put on tail of queue */ + if (dt->dt_sndqtail) { + dt->dt_sndqtail->q_next = q; /* Queue exists */ + dt->dt_sndqtail = q; /* Update ptr to tail */ + /* Assume xfer in progress, no need to restart it */ + + /* Ring doorbell always, or only if no xfer in progress? + ** For now, always ring, just in case. + */ + dt->dt_cond |= DTE_CI10DB; /* Set 10 doorbell */ + dte_pi(dt); + + } else { + dt->dt_sndq = q; /* No queue, create it */ + dt->dt_sndqtail = q; /* Update ptr to tail */ + + /* No xfr in progress, so start one. Rings doorbell. */ + dte_10start(dt); /* Start xfer going */ + } + return TRUE; +} + +/* Start xfer going */ +static void dte_10start(register struct dte *dt) +{ + register struct dteq_s *q; + register w10_t w; + + if (!(q = dt->dt_sndq)) + return; + + dt->dt_snd_state = DTSND_HDR; /* Init our sending state */ + + /* Modify 11's to-10 region: + ** STS has its TO10IC byte incremented. (FE TO11QC, misnomer) + ** QSIZE set to 0,0,<# bytes in direct pkt xfer>. + ** Sets up any internal state needed to properly handle 10's DATAO. + ** Then triggers To-10 doorbell! + ** WARNING!!!! This code clobbers DTE_CMT_IP and DTE_CMT_TOT in the + ** RH of the STS word... must preserve them if/when that becomes + ** important to support indirect xfers! + */ + RHSET(dt->dt_snd_sts, (RHGET(dt->dt_snd_sts) + (1<<8)) + & (DTE_CMT_10IC | DTE_CMT_11IC)); + if (dt->dt_dv.dv_debug) + fprintf(dt->dt_dv.dv_dbf, "[dte_10start: sts %lo,,%lo]", + (long)LHGET(dt->dt_snd_sts), + (long)RHGET(dt->dt_snd_sts)); + + dte_store(dt, dt->dt_dt10off + DTE_CMTW_STS, dt->dt_snd_sts); + + LRHSET(w, 0, DTE_QMH_SIZ + q->q_bbcnt); /* Total direct data this xfer */ + dte_store(dt, dt->dt_dt10off + DTE_CMTW_CNT, w); + + dt->dt_cond |= DTE_CI10DB; /* Set to-10 doorbell */ + dte_pi(dt); /* Trigger 10 PI */ +} + +/* Date/time hackery so 11 can send true date/time to 10. +** The From-11 format used here differs from that sent by a real 11. +** A real -11 sends a word-indirect mode packet with the date/time info +** starting in the word-indirect block. +** But this code sends a byte direct packet with the date/time info +** starting with the 1st-word data. +** This is done for simplicity. +** +** Also, note a static buffer is used. This should be OK even if +** multiple DTEs or packets are involved, since we always want to +** send the most recent time data! +*/ +#define DTM_LEN 8 +static void dte_dtmsend(register struct dte *dt) +{ + static unsigned char dtmbuf[DTM_LEN]; + struct tm t; + register unsigned int i; + int valid; + + /* Fill out data for packet */ + if (os_tmget(&t)) { + i = t.tm_year + 1900; /* Make year full A.D. quantity */ + dtmbuf[0] = (i >> 8) & 0377; /* Set high byte of year */ + dtmbuf[1] = (i & 0377); /* Set low byte */ + + dtmbuf[2] = t.tm_mon; /* Set month (0-11) */ + dtmbuf[3] = t.tm_mday - 1; /* Set day-of-month (0-31) */ + dtmbuf[4] = (t.tm_wday+6) % 7; /* Set day-of-week */ + /* DEC: 0=Mon, Unix: 0=Sun */ + dtmbuf[5] = (t.tm_isdst ? 0200 : 0) + | 8; /* Use PST for now (hack hack) */ + + /* Time is secs/2 to fit in a 16-bit word */ + i = (((((long)t.tm_hour * 60) + t.tm_min) * 60) + t.tm_sec) >> 1; + dtmbuf[6] = (i >> 8) & 0377; + dtmbuf[7] = (i & 0377); + + valid = MASK16; /* 1st byte non-Z means date/time valid! */ + } else { + valid = 0; /* 1st byte 0 means date/time invalid */ + memset(dtmbuf, 0, DTM_LEN); /* Clear rest of bytes */ + } + + dte_10qpkt(dt, + SWAB(DTE_QFN_HTD), /* Function 012 Here is ToD */ + SWAB(DTE_QDV_CLK), /* Use clock as "device" */ + valid, /* 1st wd is valid flag */ + DTM_LEN, 0, dtmbuf); /* Send date/time data! */ + + dt->dt_dtmsent = TRUE; /* Say date/time was sent! */ +} + +/* External for DVCTY code to get into here. Sorta hackish for now. +** Later spiff up with string input for greater efficiency. +*/ +extern int cty_debug; /* Crock */ + +int dte_ctysin(int cnt) +{ + register struct dte *dt = &dvdte[0]; /* Assumption for now */ + register int ch; + + /* If no DTE defined yet or no input, just return */ + if (!ndtes || (ch = os_ttyin()) < 0) /* Get single char */ + return 0; /* None left */ + + dt = &dvdte[0]; /* Assumption for now */ + if (dt->dt_secptcl) { + /* Do secondary protocol CTY input */ + register vmptr_t vpf, vpc; + register w10_t w; + + vpf = vm_physmap(cpu.mr_ebraddr + DTEE_MTI); /* Mon TTY in flg */ + vpc = vm_physmap(cpu.mr_ebraddr + DTEE_F11); /* From-11 data */ + if (op10m_skipn(vm_pget(vpf))) { + /* If flag non-zero, 10 hasn't picked up the char yet */ + fprintf(stderr, "[DTEI: %o (old %o!)]", ch, (int)vm_pgetrh(vpc)); + } else if (cty_debug) + fprintf(stderr, "[DTEI: %o]", ch); + + /* Drop char in special DTE EPT communication area */ + LRHSET(w, 0, ch); + vm_pset(vpc, w); + + /* Tell 10 that stuff is there */ + op10m_seto(w); + vm_pset(vpf, w); + + dt->dt_cond |= DTE_CI10DB; /* Set to-10 doorbell */ + dte_pi(dt); /* Trigger 10 PI */ + + } else { + /* Do primary protocol CTY input */ + + if (! dte_10qpkt(dt, + SWAB(DTE_QFN_HLC), /* Queue a line input packet */ + SWAB(DTE_QDV_CTY), /* from CTY device */ + SWAB((DTE_DLS_CTY<<8) | ch), /* Line 0, data */ + 0, 0, NULL)) { /* Nothing else in msg */ + fprintf(stderr, "[DTEI: %o dropped]", ch); + } else if (cty_debug) + fprintf(stderr, "[DTEI: %o]", ch); + } + + return cnt-1; /* One less input char! */ +} + +static int ctyalloc = 10; /* Hack for now */ + + +/* Send ACK for CTY output, with possible delay. +*/ +static void dte_ctyack(register struct dte *dt) +{ + if (dt->dt_dlyackms) { + if (!dt->dt_dlyackf) { /* Already awaiting timeout? */ + clk_tmractiv(dt->dt_dlyack); /* No, make it active again */ + dt->dt_dlyackf = TRUE; /* Timer's ticking... */ + } +#if 0 + else fprintf(stderr, "[DTE: ack buildup]\r\n"); +#endif + return; + } + dte_ctyiack(dt); /* No delay, do immediate ACK */ +} + +static void dte_ctyiack(register struct dte *dt) +{ + if (!dte_10qpkt(dt, + SWAB(DTE_QFN_LBE), /* Function 17 Line Buffer Empty */ + SWAB(DTE_QDV_CTY), + SWAB(DTE_DLS_CTY), /* CTY Line */ + 0, 0, NULL)) { /* No other data */ + fprintf(dt->dt_dv.dv_dbf, "[DTE: CTY ack failed]"); + ctyalloc = 10; + } else + ctyalloc = 10; /* Was 100 but was causing wedging... */ +} + + +#define DTE_CTYPUTC(dt, c) \ + if (--((dt)->dt_ctyocnt) < 0) { \ + dte_ctyforce(dt); /* Force out & reset buffer */\ + (dt)->dt_ctyocnt--; \ + } \ + /* Deposit in buffer, masking off stupid T20 parity */\ + *((dt)->dt_ctyocp)++ = ((c) & 0177) + + +static void dte_ctyout(register struct dte *dt, register int ch) +{ +#if 1 + if (cty_debug) + fprintf(stderr, "[DTEO: %o]", ch); +#else + if (dt->dt_dv.dv_debug) + fprintf(dt->dt_dv.dv_dbf, "[DTEO: %o]", ch); +#endif + + DTE_CTYPUTC(dt, ch); + +#if 0 /* Sigh, keeps wedging anyway */ + if (--ctyalloc <= 0) + dte_ctyack(dt); +#endif +} + +static void dte_ctysout(register struct dte *dt, + register unsigned char *cp, + register int len) +{ +#if 1 + if (cty_debug) + fprintf(stderr, "[DTESO: %d \"%.*s\"]\r\n", len, len, cp); +#else + if (dt->dt_dv.dv_debug) + fprintf(dt->dt_dv.dv_dbf, "[DTESO: %d \"%.*s\"]\r\n", len, len, cp); +#endif + + while (--len >= 0) { + DTE_CTYPUTC(dt, *cp++); + } +} + +/* Force out anything buffered for CTY. Blocks! +*/ +static void dte_ctyforce(register struct dte *dt) +{ + register int chunk; + +#if 0 + if (dt->dt_dv.dv_debug) + fprintf(dt->dt_dv.dv_dbf, "[DTEFRC: %d \"%.*s\"]\r\n", len, len, cp); +#endif + chunk = sizeof(dt->dt_ctyobuf) - dt->dt_ctyocnt; + if (chunk > 0) { + os_ttysout(dt->dt_ctyobuf, chunk); + } + dt->dt_ctyocnt = sizeof(dt->dt_ctyobuf); + dt->dt_ctyocp = dt->dt_ctyobuf; +} + +#endif /* KLH10_DEV_DTE Moby conditional */ + diff --git a/src/dvdte.h b/src/dvdte.h new file mode 100644 index 0000000..4743db7 --- /dev/null +++ b/src/dvdte.h @@ -0,0 +1,481 @@ +/* DVDTE.H - DTE20 PDP-10/11 Interface definitions +*/ +/* $Id: dvdte.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dvdte.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* References: + [PRM] 6/82 PRM p.3-62 + [DTE] EK-DTE20-UD-004 "DTE20 Ten-Eleven Interface Unit Description" + 3rd edition Oct 1976 + [T20] APRSRV.MAC, PROLOG.MAC +*/ + +#ifndef DVDTE_INCLUDED +#define DVDTE_INCLUDED 1 + +#ifdef RCSID + RCSID(dvdte_h,"$Id: dvdte.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Only externally visible entry point for DTE driver + */ +#include "kn10dev.h" +extern struct device * dvdte_create(FILE *f, char *s); + +/* Plus hackish entry for DVCTY use */ +extern int dte_ctysin(int cnt); + + +#define DTE_NMAX 4 /* Max number of DTEs possible */ +#define DTE_NSUP 1 /* Number supported for now */ + +/* DTE device #s */ + +#define DTE_DEV(n) (0200+((n)<<2)) /* 200, 204, 210, 214 */ + +/* DTE Control block offsets in EPT */ + +#define DTE_CB(n) (0140+(8*(n))) /* EPT offset for DTE block N */ + +#define DTE_CBOBP 0 /* Output byte pointer (to 11) */ +#define DTE_CBIBP 1 /* Input byte pointer (from 11) */ +#define DTE_CBINT 2 /* Vector interrupt instruction */ + /* 3 Unused */ +#define DTE_CBEPW 4 /* Examine Protect Word (size of 11-exam area) */ +#define DTE_CBERW 5 /* Examine Relocation (addr of 11-exam area) */ +#define DTE_CBDPW 6 /* Deposit Protect Word (size of 11-depo area) */ +#define DTE_CBDRW 7 /* Deposit Relocation Word (addr of 11-depo area) */ + +/* NOTE: All refs via the BPs are to executive virtual memory, sec 0; +** this is how the ucode is written. +*/ + + +/* DTE20 DATAO defs [DTE p.1-25] */ + /* 0-22 MBZ, reserved by DEC */ +#define DTE_TO10IB 010000 /* 23 To-10 "I" bit */ +#define DTE_TO10BC 07777 /* 24-35 To-10 Byte Count (negative) */ + /* If TO10IB is set, DTE interrupts both 10 and 11 + ** when receive xfer done, else just 10. + */ + +/* DTE20 CONI defs [DTE p.1-26] */ + +#define DTE_CIRM 0100000 /* 20 Restricted Mode */ +#define DTE_CID11 040000 /* 21 Dead-11 (Unibus AC power low) */ +#define DTE_CI11DB 020000 /* 22 TO11DB Doorbell request active */ + /* 23-25 zero */ +#define DTE_CI10DB 01000 /* 26 TO10DB Doorbell request active */ +#define DTE_CI11ER 0400 /* 27 Error during TO11 transfer */ + /* 28 - */ +#define DTE_CI11DN 0100 /* 29 TO11 transfer done */ +#define DTE_CI10DN 040 /* 30 TO10 transfer done */ +#define DTE_CI10ER 020 /* 31 Error during TO10 transfer */ +#define DTE_CIPI0 010 /* 32 DTE PI 0 enabled */ +#define DTE_CIPI 07 /* 33-35 PI channel assignment */ + +/* DTE20 CONO defs [DTE p.1-27] */ + +#define DTE_CO11DB 020000 /* 22 TO11DB Doorbell request */ +#define DTE_COCR11 010000 /* 23 Clear reload-11 button in DTE */ +#define DTE_COSR11 04000 /* 24 Set reload-11 button in DTE */ + /* 25 MBZ */ +#define DTE_CO10DB 01000 /* 26 Clear TO10DB Doorbell */ + /* 27-28 MBZ */ +#define DTE_COCL11 0100 /* 29 Clear TO11DN and TO11ER */ +#define DTE_COCL10 040 /* 30 Clear TO10DN and TO10ER */ +#define DTE_COPIENB 020 /* 31 Load PI chan and PI0 enable bit */ +#define DTE_COPI0 DTE_CIPI0 +#define DTE_COPI DTE_CIPI + + +/* DTE DATAO defs [DTE p.1-25] */ + /* 0-22 MBZ */ +#define DTE_DO10IB 010000 /* 23 I-bit, interrupt 11 after xfer */ +#define DTE_DO10BC 07777 /* 24-35 Negative byte count for xfer */ + +/* EPT locations for DTE secondary protocol. +** +** These appear to be special locations solely for use by the +** Master DTE and no others, and only used during secondary ptcl. +** Several are only used by KLDCP (Diagnostic Control Program) rather +** than the normal RSX20F. +** +** Because these defs come from T20 STG.MAC the names are +** retained for consistency. +*/ + +#define DTEE_FLG 0444 /* OPERATION COMPLETE FLAG */ + /* Cleared by 10 prior to setting CMD (for some) */ + /* Set -1 by 11 after CMD handled (for some) */ +#define DTEE_CKF 0445 /* CLOCK INTERRUPT FLAG */ + /* Set -1 by 11 each 60Hz tick, if clock enabled */ +#define DTEE_CKI 0446 /* CLOCK INTERRUPT INSTRUCTION */ +#define DTEE_T11 0447 /* TO 11 ARGUMENT */ +#define DTEE_F11 0450 /* FROM 11 ARGUMENT */ +#define DTEE_CMD 0451 /* COMMAND WORD */ + /* Set by 10, executed by 11 when 11DB rung */ +#define DTEE_SEQ 0452 /* DTE20 OPERATION SEQUENCE NUMBER */ +#define DTEE_OPR 0453 /* OPERATION IN PROGRESS FLAG */ +#define DTEE_CHR 0454 /* LAST TYPED CHARACTER */ +#define DTEE_TMD 0455 /* MONITOR TTY OUTPUT COMPLETE FLAG */ + /* Set 0 by 10 prior to DTECMD_MNO command */ + /* Set -1 by 11 when DTECMD_MNO is done (but note */ + /* RSX20F uses some bits to report options to 10) */ +#define DTEE_MTI 0456 /* MONITOR TTY INPUT FLAG */ + /* Set -1 by 11 when F11 contains TTY input char */ + /* Set 0 by 10 when char read (allow more tty in) */ +#define DTEE_SWR 0457 /* CONSOLE SWITCH REGISTER */ + + +/* RSX20F option bits: + + Later versions of RSX20F set DTEE_TMD to a bitmask of options, +rather than simply -1. TOPS-10 examines these bits to see whether certain +features are supported or not. TOPS-20 doesn't use them at all. +The DTEPRM source defines the following four bits: + +*/ +#define DTE_20FOPT_GDT 01 /* 1B35 DF.GDT - Zero if have DTECMD_RTM */ +#define DTE_20FOPT_CSH 02 /* 1B34 DF.CSH - Cache enabled by KLI */ + /* 1=on, 0=off */ +#define DTE_20FOPT_8BA 04 /* 1B33 DF.8BA - Zero if can support */ + /* 8-bit enable/disable on DLS lines */ +#define DTE_20FOPT_ODN 0100000 /* 1B20 DF.ODN - TTY output done */ + /* Symbol unused; apparently just to make sure + ** the word is non-zero when tty output function completes. + */ + +/* T20 Monitor "KLDTE" protocol for console TTY */ + +/* Note: if DTE_CBEPW is non-zero (at least for the master DTE) +** then implicitly "primary protocol" is in effect. +** I assume that if zero, then DTE cannot examine anything, and we're +** using "secondary protocol" which is what DDT uses. +** +** Secondary protocol is also known as "monitor mode" TTY I/O. +** A command (.DTMMC) via DTECMD must also be sent to inform 11 of this. +** +** Another command (.DTNMC) is used to leave monitor mode TTY I/O, +** if primary protocol is restored by setting EPW non-zero. +*/ + +/* Command word values for DTEE_CMD above. +** I've adopted the PROLOG definitions since those are the most common. +** APRSRV has more complete defs which aren't really used. +** DDT's hint that they came from somewhere else, but dunno where. +*/ +#define DTECMDF_CMD (017<<8) /* Command code in DTECMD */ +#define DTECMDF_CHR 0377 /* TTY output byte in DTECMD */ + +/* RSX20F provides for a "secondary protocol" which is something very +** simple for boot/ddt/standalone exec programs. Only four DTE commands +** are recognized in secondary protocol mode (10,11,12,13). Actually, +** any command that isn't one of 11,12,13 is interpreted as 10 (output +** low byte to CTY)! +** +** It's unclear whether the other DTECMD values hinted at are +** actually used, or if they were old commands later obsoleted. I can't +** find evidence for them in RSX20F. They probably come from KLDCP. +*/ +#define DTECMD_MNO (010<<8) /* Output char (low 8 bits) if monitor mode */ + /*(PROLOG:DTEMNO)(APRSRV:DTEMNO)(DDT:.DTMTO)*/ + /* (DTEPRM:DT.MTO) */ +#define DTECMD_EMP (011<<8) /* Enter Monitor TTY Protocol (leave prim) */ + /* Or: Enter Secondary Protocol */ + /*(PROLOG:DTEEMP)(APRSRV:DTEMMN)(DDT:.DTMMC)*/ + /* (DTEPRM:DT.ESP) */ +#define DTECMD_EPP (012<<8) /* Enter Primary Protocol (leave mon) */ + /* Or: Leave Secondary Protocol */ + /*(PROLOG:DTEEPP)(APRSRV:DTEMMF)(DDT:.DTNMC)*/ + /* (DTEPRM:DT.LSP) */ + /* Low bit (1B35) (DTEPRM:DT.RST) used to + ** indicate comm rgn was reset. + */ +#define DTECMD_RTM (013<<8) /* Get date/time info */ + /* (APRSRV:DTERMM) */ + /* (DTEPRM:DT.GDT) */ + /* Used by T10 if DTE_20FOPT_GDT says OK. */ + /* T20 doesn't use. */ + + +/* More info about DTECMD commands, from T20 APRSRV. +** These symbols are totally unused by the T20 monitor except for +** DTEMNO (duplicate of PROLOG's) and DTEMMN (used once in APRSRV). + + DTETTO==0B27 ;TTY OUTPUT [DIAMON: chars, 26] + ; 1 - PROGRAM CONTROL, NOT USED [DIAMON: 406,407,414] + DTECOF==2B27+0 ;CLOCK OFF + DTECON==2B27+1 ;CLOCK ON + ; 3 - SWITCHES, NOT USED [DIAMON] + ; 4 - TTY OUTPUT, SAME AS 0? + ; 5 - TTY INPUT, NOT USED + DTEPTN==6B27+0 ;PRINT NORMAL [DIAMON: Clear DDT input mode] + DTEPTF==6B27+1 ;PRINT FORCED + DTEDDI==7B27 ;DDT INPUT [DIAMON] + DTEMNO==10B27 ;MONITOR TTY OUTPUT + DTEMMN==11B27 ;MONITOR MODE ON + DTEMMF==12B27 ;MONITOR MODE OFF + DTERMM==13B27 ;READ MONITOR MODE +*/ + +/* KLDCP - (KL Diagnostics Control Program?) secondary ptcl commands +** +** The following DTE commands are apparently only used when KLDCP is running in +** the 11 instead of RSX20F. They are used by the diagnostics monitor code +** (DIAMON, D20MON), but no symbols are defined for them. +** +** Some have T20 APRSRV symbols, although nothing in the T20 monitor uses them. +*/ + +/* Summary of known KLDCP cmds used by DIAMON, SUBKL, SUBRTN: + +KLDCP stuff: + +TTY output: + +0+char char - Output char to TTY +0+^V 026 - "Flush KLDCP output buffer" + +Program control: + +1+cmd 401 - "Fatal program error in KL10" (then tries to jump to DDT) +1+cmd 402 - "Error halt in KL10" +1+cmd 403 - "End of program notification" +1+cmd 404 - "End of pass notification" +1+cmd 405 - "Get clock default word": (Stores result in CLKDFL) + 3B35 - Clock rate: 0=Full, 1=1/2, 2=1/4, 3=1/8 + 3B32 - Clock source: 0=Normal, 1=Fast, 2=External, 3=Unused + B0 - Cache 0 enabled + B1,B2,B3 ditto for Cache 1,2,3. +1+cmd 406 - "File Lookup Command" (returns value in F11) +1+cmd 407 - "File Read Command" (returns word of ASCII in F11) + (-1 if EOF) +1+cmd 414 - "File Read 8-bit Command" (returns 8-bit byte in F11) + (-1 if EOF) +Clock control: (FE clock ticks once per 16.67 ms (60HZ)) + +2+cmd 1000 - Turn clock off (disable). +2+cmd 1001 - Turn clock on (enable). + When it goes off, sets $DTCLK (445) NZ and sends To-10 DB. +2+cmd 1002 - Enable clock with wait: + $DTT11 (447) contains # ticks to wait, + $DTCLK (445) set NZ when clock goes off (and sends DB to 10) +2+cmd 1003 - Read clock count since last enabled, returns in F11. + + +3 1400 - "Get switches" +5 2400 - Get TTY input char, wait for it (returns 0 if time out) + Invoked in SUBKL; timeout appears to be defined in 11. + 10 thinks it defaults to 180 sec. + Also appears to be expected to echo. +6 3000 - "Clear DDT input mode" +7 3400 - "DDT Input mode" + +*/ + +#define DTECMD_DCP_CTYO (00<<8) /* Output low 8 bits to CTY */ + /* If low byte == 026 (ctl-V) then acts as + ** "Flush KLDCP output buffer" */ + /* (APRSRV:DTETTO) */ +#define DTECMD_DCP_PGM (01<<8) /* Program control; low byte subcmd */ +#define DTECMD_DCP_CLK (02<<8) /* Clock control; low byte subcmd */ + /* (APRSRV:DTECOF/DTECON) */ +#define DTECMD_DCP_RDSW (03<<8) /* Read data switches into F11 word */ +#define DTECMD_DCP_TIW (05<<8) /* TTY input, with wait */ +#define DTECMD_DCP_CLDI (06<<8) /* "Clear DDT input mode") */ + /* (APRSRV:DTEPTN/DTEPTF) */ +#define DTECMD_DCP_DDTIN (07<<8) /* "Use DDT input mode" */ + /* Used to read CTY input. Sets F11 to CTY input char + ** AND ECHOES IT! If no char, sets F11 to 0. + */ + + +/* Clock sub-commands */ +#define DTECMD_DCPCLK_OFF 0 /* Clock: Turn off (disable). */ +#define DTECMD_DCPCLK_ON 1 /* Clock: Turn on (enable). */ + /* When it fires, sets $DTCLK (445) NZ + ** and sends To-10 DB. */ +#define DTECMD_DCPCLK_WAIT 2 /* Clock: Turn on with wait */ + /* $DTT11 (447) contains # ticks to wait + ** before firing */ +#define DTECMD_DCPCLK_READ 3 /* Clock: Read count */ + /* Reads clock count since last enabled, + ** returns in F11. */ + + +/* DTE 10-11 Protocol Comm region definitions + +Important orientation & terminology notes: + + COMBUF:
+
+
+ COMBAS: + 10's (proc #0) own comreg: + 10's fixed: 16 (COMDAT) wds ; 10's own stuff + DTE0 to-11: 8 (COMRGN) wds ; For its comms TO this 11 + DTE1 to-11: 8 (COMRGN) wds ; " + DTE2 to-11: 8 (COMRGN) wds ; " + DTE3 to-11: 8 (COMRGN) wds ; " + + 11#1's own comreg: + #1's fixed: 16 (COMDAT) wds ; 11#1's fixed stuff + DTE0 to-11: 8 (COMRGN) wds ; For its comms TO the 10 + 11#2's own comreg: + #2's fixed: 16 (COMDAT) wds + DTE1 to-11: 8 (COMRGN) wds + 11#3's own comreg: + #3's fixed: 16 (COMDAT) wds + DTE2 to-11: 8 (COMRGN) wds + 11#4's own comreg: + #4's fixed: 16 (COMDAT) wds + DTE3 to-11: 8 (COMRGN) wds + + + "Fixed" part of region is also called "owned" part. + "Dynamic" part is also called "to" or "per processor" part. + + All relative addresses in the various com regions are relative to + COMBAS! +*/ + +/* COMBUF header word - at offset 0 in examine reloc area. +** This word allows the 11 to identify itself and locate COMBAS, +** from which it can find its own comreg. +*/ +#define DTE_CMP_CPUN 03700 /* LH: Processor # {1,2,3,4} */ +#define DTE_CMP_11CDOFF 0177777 /* RH: Offset from base to 11's comdat */ + +/* Compute offset from DTE's examine-reloc to combase */ +#define DTE_CMBAS(w) (((LHGET(w)>>6)&037)+1) + + +/* "Own" COMDAT definitions - fixed part of owned comregion. +** Size of 10's fixed area can actually vary from that for the 11s +** but in practice it's always 16 words. +*/ +#define DTE_CMOW_KAC 5 /* Wd offset: keepalive count */ + + +/* "To" COMRGN definitions - dynamic part of owned comregion. +** Again, this could vary but is always 8 words. +** Most references are to this part of the comregions since that +** is where stuff happens. +*/ + +#define DTE_CMTW_0 0 /* Wd offset: various static stuff */ +# define DTE_CMT_PRO 0400000 /* LH: 1 IF THIS IS CONNECTION TO A -10 */ +# define DTE_CMT_DTE 0200000 /* LH: 1 IF A DTE IS CONNECTING THESE TWO */ +# define DTE_CMT_DTN 0140000 /* LH: - DTE number 0-3 */ +# define DTE_CMT_VRR 076 /* LH: PROTOCOL IN USE BY THE TWO PROCESSORS */ + /* Normally .VN20F == 0 */ +# define DTE_CMT_SZ 01600000 /* LH+RH: Size of block in 8-wd units */ +# define DTE_CMT_PNM 0177777 /* RH: Processor number (+1) */ + +#define DTE_CMTW_PPT 1 /* Wd offset: Rel Addr of assoc'd proc's own comrgn */ + /* Note 10's fixed comrgn has reladdr 0 */ + +#define DTE_CMTW_STS 2 /* Wd offset: status bits */ +# define DTE_CMT_PWF 0400000 /* LH: POWER FAIL INDICATOR */ +# define DTE_CMT_L11 0200000 /* LH: LOAD-11 request indicator */ +# define DTE_CMT_INI 0100000 /* LH: INI BIT FOR MCB PROTOCOL ONLY */ +# define DTE_CMT_TST 040000 /* LH: VALID EXAMINE BIT */ +# define DTE_CMT_QP 020 /* LH: 1 IF USING QUEUED PROTOCOL (.VN20F) */ +# define DTE_CMT_FWD 01 /* LH: SAYS TO DO FULL WORD TRANSFER */ +# define DTE_CMT_IP 0400000 /* RH: INDIRECT POINTER IS SET UP */ +# define DTE_CMT_TOT 0200000 /* RH: TOIT bit */ +# define DTE_CMT_10IC 0177400 /* RH: TO10IC for queue transfers */ +# define DTE_CMT_11IC 0377 /* RH: TO11IC for queue transfers */ + +#define DTE_CMTW_CNT 3 /* Wd offset: queue counts */ +# define DTE_CMT_QCT 0177777 /* RH: # of bytes in current direct transfer */ +# define DTE_CMT_PCT ((0177777)<<16) /* LH+RH: # of bytes in entire msg */ + /* Cnt of all bytes in packet (incl all indirect data). */ + +#define DTE_CMTW_RLF 4 /* Wd offset: RELOAD PARAMETER FOR THIS -11 */ +#define DTE_CMTW_KAK 5 /* Wd offset: MY COPY OF HIS CMKAC (keepalive cnt) */ + + +/* Defs for RSX20F queued message protocol. +** Header is 10 8-bit bytes transferred in byte mode. +*/ +#define DTE_QMH_SIZ 10 /* # bytes in message header */ +#define DTE_QMH_INDIRF 0100000 /* High bit of function is "indirect" flag */ + + +/* Function codes (to-11 unless otherwise noted) */ + +#define DTE_QFN_LCI 01 /* LINE COUNT IS */ +#define DTE_QFN_ALS 02 /* CTY device alias */ +#define DTE_QFN_HSD 03 /* HERE IS STRING DATA */ +#define DTE_QFN_HLC 04 /* HERE ARE LINE CHARACTERS */ +#define DTE_QFN_RDS 05 /* REQUEST DEVICE STATUS */ +#define DTE_QFN_SDO 06 /* SPECIAL DEVICE OPERATION */ +#define DTE_QFN_STS 07 /* HERE IS DEVICE STATUS */ +#define DTE_QFN_ESD 010 /* ERROR ON DEVICE */ +#define DTE_QFN_RTD 011 /* REQUEST TIME OF DAY */ +#define DTE_QFN_HTD 012 /* HERE IS TIME OF DAY */ +#define DTE_QFN_FDO 013 /* FLUSH OUTPUT (SENT TO 11 ONLY) */ +#define DTE_QFN_STA 014 /* SEND TO ALL (SENT TO 11 ONLY) */ +#define DTE_QFN_LDU 015 /* A LINE DIALED UP (FROM 11 ONLY) */ +#define DTE_QFN_LHU 016 /* A LINE HUNG UP OR LOGGED OUT */ +#define DTE_QFN_LBE 017 /* LINE BUFFER BECAME EMPTY */ +#define DTE_QFN_XOF 020 /* XOF COMMAND TO THE FE */ +#define DTE_QFN_XON 021 /* XON COMMAND TO THE FE */ +#define DTE_QFN_SPD 022 /* SET TTY LINE SPEED */ +#define DTE_QFN_HLA 023 /* HERE IS LINE ALLOCATION */ +#define DTE_QFN_HRW 024 /* HERE IS -11 RELOAD WORD */ +#define DTE_QFN_ACK 025 /* GO ACK ALL DEVICES AND UNITS */ +#define DTE_QFN_TOL 026 /* TURN OFF/ON LINE (0 off, 1 on) */ +#define DTE_QFN_EDR 027 /* ENABLE/DISABLE DATASETS */ +#define DTE_QFN_LTR 030 /* LOAD TRANSLATION RAM */ +#define DTE_QFN_LVF 031 /* LOAD VFU */ +#define DTE_QFN_MSG 032 /* SUPPRESS SYSTEM MESSAGES TO TTY */ +#define DTE_QFN_KLS 033 /* SEND KLINIK DATA TO THE -11 */ +#define DTE_QFN_XEN 034 /* ENABLE XON (SENT TO 11 ONLY) */ +#define DTE_QFN_BKW 035 /* BREAK-THROUGH WRITE */ +#define DTE_QFN_DBN 036 /* DEBUG MODE ON */ +#define DTE_QFN_DBF 037 /* DEBUG MODE OFF */ +#define DTE_QFN_SNO 040 /* IGNORE NEW CODE FOR SERIAL NUMBERS */ + + + +/* FRONT END PSEUDO DEVICES */ + +#define DTE_QDV_CTY 01 /* CTY on the DL11-C */ +#define DTE_QDV_DL1 02 /* DL11-C (or -E) on the master -11 */ +#define DTE_QDV_DH1 03 /* DH11 lines 1-N */ +#define DTE_QDV_DLS 04 /* Generic Data Line identifier */ + /* All lines represented by unit #s of this + ** "device", whether DL, DH, etc. + */ +#define DTE_QDV_LPT 05 /* Line printer */ +#define DTE_QDV_CDR 06 /* Card Reader */ +#define DTE_QDV_CLK 07 /* Clock (not defined in T20) */ +#define DTE_QDV_FE 010 /* Software Front End device */ + +/* KLH10 configurable parameter */ +#define DTE_DLS_CTY 0 /* DLS Line # to use for CTY */ + + +#endif /* ifndef DVDTE_INCLUDED */ diff --git a/src/dvdz11.c b/src/dvdz11.c new file mode 100644 index 0000000..ed592b1 --- /dev/null +++ b/src/dvdz11.c @@ -0,0 +1,129 @@ +/* DVDZ11.C - Emulates DZ11 terminal mpxr device for KS10 +*/ +/* $Id: dvdz11.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dvdz11.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* Just a dummy for now; writes do nothing and reads return 0. +*/ + +#include "klh10.h" + +#if !KLH10_DEV_DZ11 && CENV_SYS_DECOSF + /* Stupid gubbish needed to prevent OSF/1 AXP compiler from + ** halting merely because compiled file is empty! + */ +static int decosfcclossage; +#endif + +#if KLH10_DEV_DZ11 /* Moby conditional for entire file */ + +#include "kn10def.h" +#include "kn10dev.h" +#include "dvuba.h" +#include "dvdz11.h" + +#ifdef RCSID + RCSID(dvdz11_c,"$Id: dvdz11.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +#ifndef NDZ11S_MAX /* Max # of DZ11s to support */ +# define NDZ11S_MAX 4 +#endif + +struct dzdev { + struct device dz_dv; + int dz_flags; + + /* More stuff later if supporting this crock is ever deemed worthwhile. + Personally I'd go with a DH11 for terminal support. + */ +}; + + /* Flags for dz_dflags */ +#define DVDZ11F_RPI 01 /* Receive side has PI request */ +#define DVDZ11F_TPI 02 /* Transmit side has PI request */ + + +static void dz11_init(struct device *d); +static dvureg_t dz11_pivec(register struct device *d); +/* +static dvureg_t dz11_read(); +static void dz11_write(); +*/ + +static int ndz11s = 0; /* Bumped by one each config call! */ +struct dzdev dvdz11[NDZ11S_MAX]; /* External for easier debugging */ + + +struct device * dvdz11_init(FILE *f, char *s) +{ + register struct device *d; + register int n; + + if ((n = ndz11s) >= NDZ11S_MAX-1) { + fprintf(f, "Cannot add another DZ11, limit %d.\n", NDZ11S_MAX); + return NULL; + } + ++ndz11s; + d = (struct device *)&dvdz11[n]; /* Assign a device */ + iodv_setnull(d); /* Set up as null device */ + + d->dv_addr = UB_DZ11(n); /* 1st valid Unibus address */ + d->dv_aend = UB_DZ11END(n); /* 1st invalid Unibus address */ + d->dv_brlev = UB_DZ11_BR; /* BR level */ + d->dv_brvec = UB_DZ11_VEC(n); /* BR vector */ + d->dv_pivec = dz11_pivec; /* PI: Get vector */ +#if 0 + d->dv_read = dz11_read; /* Read Unibus register */ + d->dv_write = dz11_write; /* Write Unibus register */ +#endif + + dz11_init(d); + + return d; +} + +static dvureg_t dz11_pivec(register struct device *d) +{ + register int vec = 0; + + /* Give priority to input side (later can alternate) */ + if (d->dv_dflags & DVDZ11F_RPI) { + d->dv_dflags &= ~DVDZ11F_RPI; + vec = d->dv_brvec; /* Receive vector same as base */ + } else if (d->dv_dflags & DVDZ11F_TPI) { + d->dv_dflags &= ~DVDZ11F_TPI; + vec = d->dv_brvec + 4; /* Transmit vector is plus 4 */ + } + + if ((d->dv_dflags & (DVDZ11F_TPI | DVDZ11F_TPI)) == 0) + (*d->dv_pifun)(d, 0); /* Turn off interrupt request */ + + return vec; /* Return vector to use */ +} + +static void dz11_init(struct device *d) +{ +} + +#endif /* KLH10_DEV_DZ11 */ + diff --git a/src/dvdz11.h b/src/dvdz11.h new file mode 100644 index 0000000..c166d9d --- /dev/null +++ b/src/dvdz11.h @@ -0,0 +1,119 @@ +/* DVDZ11.H - DZ11 Terminal Un-Multiplexor +*/ +/* $Id: dvdz11.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dvdz11.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ +/* +** Portions of this file were derived from AI:SYSTEM;DZ11 10 +*/ + +#ifndef DVDZ11_INCLUDED +#define DVDZ11_INCLUDED 1 + +#ifdef RCSID + RCSID(dvdz11_h,"$Id: dvdz11.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +extern struct device *dvdz11_init(FILE *f, char *s); + +/* DZ11 addresses & assignments for KS10: + DZ11 Address Vector UBA# BR-Level + #1 0760010 0340 3 5 + #2 0760020 0350 3 5 + etc ... +10 ...+10 +*/ + +#define UB_DZ11_BR 5 /* BR level */ +#define UB_DZ11_VEC(n) (0340+(8*n)) /* DZ11 Interrupt Vector */ +#define UB_DZ11_RVEC(n) (DZ_VEC(n)) /* Receive */ +#define UB_DZ11_TVEC(n) (DZ_VEC(n)+4) /* Transmit */ +#define UB_DZ11(n) (0760010+(8*n)) /* DZ11 Unibus Address */ + +/* DZ11 Unibus register addresses & bits. +** These are particularly weird because of the 4 register addresses, +** two of them have different meanings depending on whether one is +** reading or writing. BSIOx and BCIOx instructions on those are +** inadvisable. +** Offset Read Write DEC Names +** 0 == RCS == CSR +** 2 RDR: RLP: RBUF LPR +** 4 == RTC: == DTR+TCR +** 6 RMS: RTD: CAR+RNG BRK+TBUF +** +Note: In T20's TTDZDV.MAC at DZHU2+n there is a BCIOB of LPR. I don't +know how this can work without erroneously reading a char, unless the +unibus is a lot more sophisticated than I thought. Since this is only +invoked when a line hangs up, and only affects 1 line out of the 8, +perhaps no one ever noticed. Sigh. --KLH +*/ + +/* +DZLNLN==3 +DZNLN==1_DZLNLN ;Number of DZ terminal lines per board +DZLNM==DZNLN-1 ;Line number mask given DZ number of TTY +*/ + +#define DZ_LM 03400 /* Line Number Mask */ +#define DZ_LS 8 /* Line number shift */ + +#define UB_DZRCS(n) (UB_DZ11(n)+0) /* Control & Status Register */ +# define DZ_CMN 010 /* Maintenance */ +# define DZ_CCL 020 /* Clear */ +# define DZ_CMS 040 /* Master Scan Enable */ +# define DZ_CRE 0100 /* Receiver Interrupt Enable */ +# define DZ_CRD 0200 /* Receiver Done */ +# define DZ_CSE 010000 /* Silo Alarm Enable */ +# define DZ_CSA 020000 /* Silo Alarm */ +# define DZ_CTE 040000 /* Transmitter Interrupt Enable */ +# define DZ_CTR 0100000 /* Transmitter Ready */ + +#define UB_DZRLP(n) (UB_DZ11(n)+02) /* Line Parameter Register */ +# define DZ_LLM 07 /* Line number mask */ +# define DZ_LCL 010 /* Character Length position */ +# define DZ_LSC 040 /* Stop code bit */ +# define DZ_LPY 0100 /* Parity bit */ +# define DZ_LOP 0200 /* Odd parity */ +# define DZ_LSP 0400 /* Speed code position */ +# define DZ_LSS 8 /* Speed code shift */ +# define DZ_LRO 010000 /* Receiver on */ + +#define UB_DZRDR(n) (UB_DZ11(n)+02) /* Read Data Register */ +# define DZ_DCM 0377 /* Character mask */ +# define DZ_DPE 010000 /* Parity Error */ +# define DZ_DFE 020000 /* Frame Error (break key) */ +# define DZ_DOR 040000 /* Overrun */ +# define DZ_DDV 0100000 /* Data valid */ + +#define UB_DZRTC(n) (UB_DZ11(n)+04) /* Transmitter Control (low byte) */ + /* & Data Terminal flags (high) */ + +#define UB_DZRTD(n) (UB_DZ11(n)+06) /* Transmitter Buffer & Break registers */ +# define DZ_TCM 0377 /* Character mask */ +# define DZ_TBM 0177400 /* Break mask */ + +#define UB_DZRMS(n) (UB_DZ11(n)+06) /* Modem status */ +# define DZ_MRI 0377 /* Ring detect */ +# define DZ_MCD 0177400 /* Carrier detect */ + + +#define UB_DZ11END(n) (UB_DZRMS(n)+2) /* First addr not used by DZ11 regs */ + +#endif /* ifndef DVDZ11_INCLUDED */ diff --git a/src/dvhost.c b/src/dvhost.c new file mode 100644 index 0000000..41c79a2 --- /dev/null +++ b/src/dvhost.c @@ -0,0 +1,334 @@ +/* DVHOST.C - Fake "Host" device to provide access to native host platform +*/ +/* $Id: dvhost.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1994, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dvhost.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* +*/ +#include "klh10.h" + +#if !KLH10_DEV_HOST && CENV_SYS_DECOSF + /* Stupid gubbish needed to prevent OSF/1 AXP compiler from + ** halting merely because compiled file is empty! + */ +static int decosfcclossage; +#endif + +#if KLH10_DEV_HOST /* Moby conditional for entire file */ + +#include /* For size_t etc */ +#include +#include +#include + +#include "kn10def.h" /* This includes OSD defs */ +#include "kn10dev.h" +#include "kn10ops.h" +#include "dvhost.h" +#include "kn10clk.h" /* Need access to clock stuff */ +#include "prmstr.h" /* For parameter parsing */ + +#ifdef RCSID + RCSID(dvhost_c,"$Id: dvhost.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +struct host { + struct device hst_dv; /* Generic 10 device structure */ +}; +static int nhosts = 0; + +#define DVHOST_NSUP 1 /* Should never be anything else! */ + +struct host dvhost[DVHOST_NSUP]; + + +/* Function predecls */ + +static int hst_conf(FILE *f, char *s, struct host *hst); + +#if KLH10_CPU_KS +static void hst_write(struct device *, /* Unibus register write */ + uint18, dvureg_t); +#else +static void hst_cono(struct device *, h10_t); /* CONO 18-bit conds out */ +# if 0 +static w10_t hst_coni(); /* CONI 36-bit conds in */ +static void hst_datao(); /* DATAO word out */ +static w10_t hst_datai(); /* DATAI word in */ +# endif +#endif /* !KLH10_CPU_KS */ + + +/* Configuration Parameters */ + +#if KLH10_CPU_KS +# define DVHOST_PARAMS \ + prmdef(HSTP_DBG, "debug"), /* Initial debug value */\ + prmdef(HSTP_BR, "br"), /* BR priority */\ + prmdef(HSTP_VEC, "vec"), /* Interrupt vector */\ + prmdef(HSTP_ADDR,"addr") /* Unibus address */ +#else +# define DVHOST_PARAMS \ + prmdef(HSTP_DBG, "debug") /* Initial debug value */ +#endif /* KLH10_CPU_KS */ + +enum { +# define prmdef(i,s) i + DVHOST_PARAMS +# undef prmdef +}; + +static char *hstprmtab[] = { +# define prmdef(i,s) s + DVHOST_PARAMS +# undef prmdef + , NULL +}; + +/* HST_CONF - Parse configuration string and set defaults. +** At this point, device has just been created, but not yet bound +** or initialized. +** NOTE that some strings are dynamically allocated! Someday may want +** to clean them up nicely if config fails or device is uncreated. +*/ +static int hst_conf(FILE *f, char *s, struct host *hst) +{ + int i, ret = TRUE; + struct prmstate_s prm; + char buff[200]; +#if KLH10_CPU_KS + long lval; +#endif + + /* First set defaults for all configurable parameters + Unfortunately there's currently no way to access the UBA # that + we're gonna be bound to, otherwise could set up defaults. Later + fix this by giving dvhost_create() a ptr to an arg structure, etc. + */ + DVDEBUG(hst) = FALSE; + + prm_init(&prm, buff, sizeof(buff), + s, strlen(s), + hstprmtab, sizeof(hstprmtab[0])); + while ((i = prm_next(&prm)) != PRMK_DONE) { + switch (i) { + case PRMK_NONE: + fprintf(f, "Unknown HOST parameter \"%s\"\n", prm.prm_name); + ret = FALSE; + continue; + case PRMK_AMBI: + fprintf(f, "Ambiguous HOST parameter \"%s\"\n", prm.prm_name); + ret = FALSE; + continue; + default: /* Handle matches not supported */ + fprintf(f, "Unsupported HOST parameter \"%s\"\n", prm.prm_name); + ret = FALSE; + continue; + + case HSTP_DBG: /* Parse as true/false boolean or number */ + if (!prm.prm_val) /* No arg => default to 1 */ + DVDEBUG(hst) = 1; + else if (!s_tobool(prm.prm_val, &DVDEBUG(hst))) + break; + continue; + +#if KLH10_CPU_KS + case HSTP_BR: /* Parse as octal number */ + if (!prm.prm_val || !s_tonum(prm.prm_val, &lval)) + break; + if (lval < 4 || lval > 7) { + fprintf(f, "HOST BR must be one of 4,5,6,7\n"); + ret = FALSE; + } else + hst->hst_dv.dv_brlev = lval; + continue; + + case HSTP_VEC: /* Parse as octal number */ + if (!prm.prm_val || !s_tonum(prm.prm_val, &lval)) + break; + if (lval < 4 || lval > 0400 || (lval&03)) { + fprintf(f, "HOST VEC must be valid multiple of 4\n"); + ret = FALSE; + } else + hst->hst_dv.dv_brvec = lval; + continue; + + case HSTP_ADDR: /* Parse as octal number */ + if (!prm.prm_val || !s_tonum(prm.prm_val, &lval)) + break; +#if 0 + if (lval < (DVHOST_REG_N<<1) || (lval&037)) { + fprintf(f, "HOST ADDR must be valid Unibus address\n"); + ret = FALSE; + } else +#endif + hst->hst_dv.dv_addr = lval; + continue; +#endif /* KLH10_CPU_KS */ + } + ret = FALSE; + fprintf(f, "HOST param \"%s\": ", prm.prm_name); + if (prm.prm_val) + fprintf(f, "bad value syntax: \"%s\"\n", prm.prm_val); + else + fprintf(f, "missing value\n"); + } + + /* Param string all done, do followup checks or cleanup */ +#if KLH10_CPU_KS +# if 0 + if (!hst->hst_dv.dv_brlev || !hst->hst_dv.dv_brvec || !hst->hst_dv.dv_addr) { + fprintf(f, "HOST missing one of BR, VEC, ADDR params\n"); +# else + if (!hst->hst_dv.dv_addr) { + fprintf(f, "HOST missing ADDR param\n"); +# endif + ret = FALSE; + } + /* Set 1st invalid addr */ + hst->hst_dv.dv_aend = hst->hst_dv.dv_addr + (DVHOST_REG_N * 2); +#endif /* KLH10_CPU_KS */ + + return ret; +} + +/* HOST interface routines to KLH10 */ + +struct device * dvhost_create(FILE *f, char *s) +{ + register struct host *hst; + + /* Parse string to determine which device to use, config, etc etc + ** But for now, just allocate sequentially. Hack. + */ + if (nhosts >= DVHOST_NSUP) { + fprintf(f, "Too many HOSTs, max: %d\n", DVHOST_NSUP); + return NULL; + } + hst = &dvhost[nhosts++]; /* Pick unused dev */ + memset((char *)hst, 0, sizeof(*hst)); /* Clear it out */ + + /* Initialize generic device part of HOST struct */ + iodv_setnull(&hst->hst_dv); /* Initialize as null device */ + +#if KLH10_CPU_KS + /* Operate as new-style Unibus device */ + hst->hst_dv.dv_write = hst_write; /* Write unibus register */ +#else + /* Operate as old-style IO-bus device */ + hst->hst_dv.dv_cono = hst_cono; /* Do CONO only */ +#endif + + /* Configure from parsed string and remember for init + */ + if (!hst_conf(f, s, hst)) + return NULL; + + return &hst->hst_dv; +} + + +#if !KLH10_CPU_KS + +/* CONO 18-bit conds out +** Args D, ERH +** Returns nothing +*/ +static insdef_cono(hst_cono) +{ + register struct host *hst = (struct host *)d; + register uint18 cond = erh; + + if (DVDEBUG(hst)) + fprintf(DVDBF(hst), "[hst_cono: %lo]\r\n", (long)erh); + + if (cond == DVHOST_CO_IDLE) + clk_idle(); /* Invoke CLK_IDLE! */ +} + +#if 0 /* Nothing else needed for now */ + +/* CONI 36-bit conds in +** Args D +** Returns condition word +*/ +static insdef_coni(hst_coni) +{ + register w10_t w; + register struct host *hst = (struct host *)d; + + if (DVDEBUG(hst)) + fprintf(DVDBF(hst), "[hst_coni: %lo,,%lo]\r\n", + (long)hst->hst_lhcond, (long)hst->hst_cond); + return w; +} + +/* DATAO word out +** Args D, W +** Returns nothing +*/ +static insdef_datao(hst_datao) +{ + register struct host *hst = (struct host *)d; + + if (DVDEBUG(hst)) + fprintf(DVDBF(hst), "[hst_datao: %lo,,%lo]\r\n", + (long)LHGET(w), (long)RHGET(w)); + +} + +/* DATAI word in +** Args D +** Returns data word +*/ +static insdef_datai(hst_datai) +{ + register struct host *hst = (struct host *)d; + register w10_t w; + + + if (DVDEBUG(hst)) + fprintf(DVDBF(hst), "[hst_datai: %lo,,%lo]\r\n", + (long)LHGET(w), (long)RHGET(w)); + return w; +} + +#endif /* 0 */ /* Nothing else needed for now */ +#endif /* !KLH10_CPU_KS */ + +#if KLH10_CPU_KS + +/* Unibus interface routines */ + +static void hst_write(struct device *d, uint18 addr, register dvureg_t val) +{ + register struct host *hst = (struct host *)d; + + if (DVDEBUG(hst)) + fprintf(DVDBF(hst), "[hst_write: %lo]\r\n", (long)val); + + if (val == DVHOST_CO_IDLE) + clk_idle(); /* Invoke CLK_IDLE! */ +} +#endif /* KLH10_CPU_KS */ + +#endif /* KLH10_DEV_HOST */ diff --git a/src/dvhost.h b/src/dvhost.h new file mode 100644 index 0000000..19274f9 --- /dev/null +++ b/src/dvhost.h @@ -0,0 +1,46 @@ +/* DVHOST.H - HOST native platform access defniitions +*/ +/* $Id: dvhost.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1994, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dvhost.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#ifndef DVHOST_INCLUDED +#define DVHOST_INCLUDED 1 + +#ifdef RCSID + RCSID(dvhost_h,"$Id: dvhost.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Only externally visible entry point for HOST driver + */ +#include "kn10dev.h" +extern struct device * dvhost_create(FILE *f, char *s); + +#define DVHOST_NSUP 1 /* Should be only 1! */ + +/* CONO bits +** Preliminary hackery... +*/ +#define DVHOST_CO_IDLE 01 /* "Op-code" to do idle hackery */ + +#define DVHOST_REG_N 1 /* If on a Unibus, only one register */ + +#endif /* ifndef DVHOST_INCLUDED */ diff --git a/src/dvlhdh.c b/src/dvlhdh.c new file mode 100644 index 0000000..9d9619d --- /dev/null +++ b/src/dvlhdh.c @@ -0,0 +1,1691 @@ +/* DVLHDH.C - ACC LH-DH IMP Interface emulation +*/ +/* $Id: dvlhdh.c,v 2.4 2001/11/19 10:47:54 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dvlhdh.c,v $ + * Revision 2.4 2001/11/19 10:47:54 klh + * Init dpimp_blobsiz when dpimp seg created. + * + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#include "klh10.h" + +#if !KLH10_DEV_LHDH && CENV_SYS_DECOSF + /* Stupid gubbish needed to prevent OSF/1 AXP compiler from + ** halting merely because compiled file is empty! + */ +static int decosfcclossage; +#endif + +#if KLH10_DEV_LHDH /* Moby conditional for entire file */ + +#include +#include + +#if KLH10_DEV_SIMP +# include /* For access(), fcntl() */ +# include +# include +# include /* For kill() */ +# include +# undef I_PUSH /* Avoid name conflicts with 10 instr ops */ +# undef I_POP +#endif + +#include "kn10def.h" +#include "kn10dev.h" +#include "prmstr.h" /* For parameter parsing */ +#include "dvuba.h" +#include "dvlhdh.h" + +#if KLH10_DEV_SIMP || KLH10_DEV_DPIMP +# include "dpimp.h" +#endif + +#ifdef RCSID + RCSID(dvlhdh_c,"$Id: dvlhdh.c,v 2.4 2001/11/19 10:47:54 klh Exp $") +#endif + +#define IMPBUFSIZ (SIH_HSIZ+SI_LDRSIZ+SI_MAXMSG+500) /* Plenty of slop */ + + +#ifndef LHDH_NSUP +# define LHDH_NSUP 1 /* Only one supported */ +#endif + +#define REG(u,name) ((u)->lh_reg[name]) + +struct lhdh { + struct device lh_dv; /* Generic 10 device structure */ + + /* LHDH-specific vars */ + + /* LHDH internal registers */ + dvureg_t lh_reg[LHR_N]; /* Storage for registers (16 bits each) */ + + /* I/O signalling flags */ + int lh_ipireq; /* Input side doing PI request */ + int lh_opireq; /* Output side doing PI request */ + int lh_inwakflg; /* Always TRUE if doing SIGURG wakeup */ + int lh_inactf; /* TRUE if input active (enabled) */ + int lh_outactf; /* TRUE if output active */ + int lh_iwcnt; /* Input word count */ + int lh_owcnt; /* Output word count */ + unsigned char *lh_iptr; /* Pointer to input data */ + unsigned char *lh_optr; /* Pointer to output data */ + + /* New clock timer stuff, for input polling */ + struct clkent *lh_chktmr; /* Timer for periodic run re-checks */ + int lh_docheck; /* TRUE if timer active */ + + /* Misc config info not set elsewhere */ + char *lh_ifnam; /* Native platform's interface name */ + int lh_dedic; /* TRUE if interface dedicated (else shared) */ + int lh_doarp; /* TRUE to do ARP hackery (if shared) */ + int lh_backlog; /* Max # input msgs to queue up in kernel */ + int lh_rdtmo; /* # secs to timeout on packetfilter reads */ + unsigned char lh_ipadr[4]; /* KLH10 IP address to filter on */ + unsigned char lh_gwadr[4]; /* Gateway IP address to use when needed */ + unsigned char lh_ethadr[6]; /* Ether address to use, if dedicated */ + + char *lh_dpname; /* Pointer to dev process pathname */ + int lh_dpidly; /* # secs to sleep when starting DP */ + int lh_dpdbg; /* Initial DP debug flag */ + +#if KLH10_DEV_DPIMP + int lh_dpstate; /* TRUE if dev process has finished its init */ + struct dp_s lh_dp; /* Handle on dev process */ + unsigned char *lh_sbuf; /* Pointers to shared memory buffers */ + unsigned char *lh_rbuf; + int lh_rcnt; /* # chars in received packet input buffer */ +#endif + +#if KLH10_DEV_SIMP + int lh_imppid; /* PID of SIMP subprocess */ + struct impio { + int io_fd; /* FD to carry out I/O on */ + unsigned char io_buf[IMPBUFSIZ]; + } lh_impi, lh_impo; /* Input & output thread devices */ +#endif +}; + +static int nlhdhs = 0; +struct lhdh dvlhdh[LHDH_NSUP]; + /* Can be static, but left external for debugging */ + +/* Function predecls */ + +static int lhdh_conf(FILE *f, char *s, struct lhdh *lh); +static int lhdh_init(struct device *d, FILE *of); +static dvureg_t lhdh_pivec(struct device *d); +static dvureg_t lhdh_read(struct device *d, uint18 addr); +static void lhdh_write(struct device *d, uint18 addr, dvureg_t val); +static void lhdh_clear(struct device *d); +static void lhdh_powoff(struct device *d); + +static void lh_incheck(struct lhdh *lh); +static void lh_clear(struct lhdh *lh); +static void lh_oint(struct lhdh *lh); +static void lh_iint(struct lhdh *lh); +static void lh_igo(struct lhdh *lh); +static void lh_ogo(struct lhdh *lh); +static void lh_idone(struct lhdh *lh); +static void lh_odone(struct lhdh *lh); +static int lh_io(struct lhdh *lh, int wrtf); +static int lh_bcopy(struct lhdh *lh, int wrtf, paddr_t mem, int wcnt); +static void showpkt(FILE *f, char *id, unsigned char *buf, int cnt); + + /* Virtual IMP low-level stuff */ +static int imp_init(struct lhdh *lh, FILE *of); +static int imp_start(struct lhdh *lh); +static void imp_stop(struct lhdh *lh); +static void imp_kill(struct lhdh *lh); +static int imp_incheck(struct lhdh *lh); +static void imp_inxfer(struct lhdh *lh); +static int imp_outxfer(struct lhdh *lh); + +/* Configuration Parameters */ + +#define DVLHDH_PARAMS \ + prmdef(LHDHP_DBG, "debug"), /* Initial debug value */\ + prmdef(LHDHP_BR, "br"), /* BR priority */\ + prmdef(LHDHP_VEC, "vec"), /* Interrupt vector */\ + prmdef(LHDHP_ADDR,"addr"), /* Unibus address */\ +\ + prmdef(LHDHP_IP, "ipaddr"), /* IP address of KLH10, if shared */\ + prmdef(LHDHP_GW, "gwaddr"), /* IP address of prime GW to use */\ + prmdef(LHDHP_EN, "enaddr"), /* Ethernet address to use (override) */\ + prmdef(LHDHP_IFC,"ifc"), /* Ethernet interface name */\ + prmdef(LHDHP_BKL,"backlog"),/* Max bklog for rcvd pkts (else sys deflt) */\ + prmdef(LHDHP_DED,"dedic"), /* TRUE= Ifc dedicated (else shared) */\ + prmdef(LHDHP_ARP,"doarp"), /* TRUE= if shared, do ARP hackery */\ + prmdef(LHDHP_RDTMO,"rdtmo"), /* # secs to timeout on packetfilter read */\ + prmdef(LHDHP_DPDLY,"dpdelay"),/* # secs to sleep when starting DP */\ + prmdef(LHDHP_DPDBG,"dpdebug"),/* Initial DP debug value */\ + prmdef(LHDHP_DP, "dppath") /* Device subproc pathname */ + + +enum { +# define prmdef(i,s) i + DVLHDH_PARAMS +# undef prmdef +}; + +static char *lhdhprmtab[] = { +# define prmdef(i,s) s + DVLHDH_PARAMS +# undef prmdef + , NULL +}; + +static int pareth(char *cp, unsigned char *adr); +static int parip(char *cp, unsigned char *adr); + +/* LHDH_CONF - Parse configuration string and set defaults. +** At this point, device has just been created, but not yet bound +** or initialized. +** NOTE that some strings are dynamically allocated! Someday may want +** to clean them up nicely if config fails or device is uncreated. +*/ + +static int +lhdh_conf(FILE *f, char *s, struct lhdh *lh) +{ + int i, ret = TRUE; + struct prmstate_s prm; + char buff[200]; + long lval; + + /* First set defaults for all configurable parameters + Unfortunately there's currently no way to access the UBA # that + we're gonna be bound to, otherwise could set up defaults. Later + fix this by giving lhdh_create() a ptr to an arg structure, etc. + */ + DVDEBUG(lh) = FALSE; + lh->lh_ifnam = NULL; + lh->lh_backlog = 0; + lh->lh_dedic = FALSE; + lh->lh_doarp = TRUE; + lh->lh_rdtmo = 1; /* Default to 1 sec timeout check */ + lh->lh_dpidly = 0; + lh->lh_dpdbg = FALSE; +#if KLH10_DEV_DPIMP + lh->lh_dpname = "dpimp"; /* Pathname of device subproc */ +#elif KLH10_DEV_SIMP + lh->lh_dpname = "simp"; /* Pathname of device subproc */ +#endif + + prm_init(&prm, buff, sizeof(buff), + s, strlen(s), + lhdhprmtab, sizeof(lhdhprmtab[0])); + while ((i = prm_next(&prm)) != PRMK_DONE) { + switch (i) { + case PRMK_NONE: + fprintf(f, "Unknown LHDH parameter \"%s\"\n", prm.prm_name); + ret = FALSE; + continue; + case PRMK_AMBI: + fprintf(f, "Ambiguous LHDH parameter \"%s\"\n", prm.prm_name); + ret = FALSE; + continue; + default: /* Handle matches not supported */ + fprintf(f, "Unsupported LHDH parameter \"%s\"\n", prm.prm_name); + ret = FALSE; + continue; + + case LHDHP_DBG: /* Parse as true/false boolean or number */ + if (!prm.prm_val) /* No arg => default to 1 */ + DVDEBUG(lh) = 1; + else if (!s_tobool(prm.prm_val, &DVDEBUG(lh))) + break; + continue; + + case LHDHP_BR: /* Parse as octal number */ + if (!prm.prm_val || !s_tonum(prm.prm_val, &lval)) + break; + if (lval < 4 || lval > 7) { + fprintf(f, "LHDH BR must be one of 4,5,6,7\n"); + ret = FALSE; + } else + lh->lh_dv.dv_brlev = lval; + continue; + + case LHDHP_VEC: /* Parse as octal number */ + if (!prm.prm_val || !s_tonum(prm.prm_val, &lval)) + break; + if (lval < 4 || lval > 0400 || (lval&03)) { + fprintf(f, "LHDH VEC must be valid multiple of 4\n"); + ret = FALSE; + } else + lh->lh_dv.dv_brvec = lval; + continue; + + case LHDHP_ADDR: /* Parse as octal number */ + if (!prm.prm_val || !s_tonum(prm.prm_val, &lval)) + break; + if (lval < (LHR_N<<1) || (lval&037)) { + fprintf(f, "LHDH ADDR must be valid Unibus address\n"); + ret = FALSE; + } else + lh->lh_dv.dv_addr = lval; + continue; + + case LHDHP_IP: /* Parse as IP address: u.u.u.u */ + if (!prm.prm_val) + break; + if (!parip(prm.prm_val, &lh->lh_ipadr[0])) + break; + continue; + + case LHDHP_GW: /* Parse as IP address: u.u.u.u */ + if (!prm.prm_val) + break; + if (!parip(prm.prm_val, &lh->lh_gwadr[0])) + break; + continue; + + case LHDHP_EN: /* Parse as EN address in hex */ + if (!prm.prm_val) + break; + if (!pareth(prm.prm_val, &lh->lh_ethadr[0])) + break; + continue; + + case LHDHP_IFC: /* Parse as simple string */ + if (!prm.prm_val) + break; + lh->lh_ifnam = s_dup(prm.prm_val); + continue; + + case LHDHP_BKL: /* Parse as decimal number */ + if (!prm.prm_val || !s_todnum(prm.prm_val, &lval)) + break; + lh->lh_backlog = lval; + continue; + + case LHDHP_DED: /* Parse as true/false boolean */ + if (!prm.prm_val) + break; + if (!s_tobool(prm.prm_val, &lh->lh_dedic)) + break; + continue; + + case LHDHP_ARP: /* Parse as true/false boolean or number */ + if (!prm.prm_val) + break; + if (!s_tobool(prm.prm_val, &lh->lh_doarp)) + break; + continue; + + case LHDHP_RDTMO: /* Parse as decimal number */ + if (!prm.prm_val || !s_todnum(prm.prm_val, &lval)) + break; + lh->lh_rdtmo = lval; + continue; + + case LHDHP_DPDLY: /* Parse as decimal number */ + if (!prm.prm_val || !s_todnum(prm.prm_val, &lval)) + break; + lh->lh_dpidly = lval; + continue; + + case LHDHP_DPDBG: /* Parse as true/false boolean or number */ + if (!prm.prm_val) /* No arg => default to 1 */ + lh->lh_dpdbg = 1; + else if (!s_tobool(prm.prm_val, &(lh->lh_dpdbg))) + break; + continue; + + case LHDHP_DP: /* Parse as simple string */ + if (!prm.prm_val) + break; + lh->lh_dpname = s_dup(prm.prm_val); + continue; + + } + ret = FALSE; + fprintf(f, "LHDH param \"%s\": ", prm.prm_name); + if (prm.prm_val) + fprintf(f, "bad value syntax: \"%s\"\n", prm.prm_val); + else + fprintf(f, "missing value\n"); + } + + /* Param string all done, do followup checks or cleanup */ + if (!lh->lh_dv.dv_brlev || !lh->lh_dv.dv_brvec || !lh->lh_dv.dv_addr) { + fprintf(f, "LHDH missing one of BR, VEC, ADDR params\n"); + ret = FALSE; + } + /* Set 1st invalid addr */ + lh->lh_dv.dv_aend = lh->lh_dv.dv_addr + (LHR_N * 2); + + /* IPADDR must always be set! */ + if (memcmp(lh->lh_ipadr, "\0\0\0\0", 4) == 0) { + fprintf(f, + "LHDH param \"ipaddr\" must be set\n"); + return FALSE; + } + + return ret; +} + +static int +parip(char *cp, unsigned char *adr) +{ + unsigned int b1, b2, b3, b4; + + if (4 != sscanf(cp, "%u.%u.%u.%u", &b1, &b2, &b3, &b4)) + return FALSE; + if (b1 > 255 || b2 > 255 || b3 > 255 || b4 > 255) + return FALSE; + *adr++ = b1; + *adr++ = b2; + *adr++ = b3; + *adr = b4; + return TRUE; +} + +static int +pareth(char *cp, unsigned char *adr) +{ + unsigned int b1, b2, b3, b4, b5, b6; + int cnt; + + cnt = sscanf(cp, "%x:%x:%x:%x:%x:%x", &b1, &b2, &b3, &b4, &b5, &b6); + if (cnt != 6) { + /* Later try as single large address #? */ + return FALSE; + } + if (b1 > 255 || b2 > 255 || b3 > 255 || b4 > 255 || b5 > 255 || b6 > 255) + return FALSE; + *adr++ = b1; + *adr++ = b2; + *adr++ = b3; + *adr++ = b4; + *adr++ = b5; + *adr = b6; + return TRUE; +} + +/* LHDH interface routines to KLH10 */ + +struct device * +dvlhdh_create(FILE *f, char *s) +{ + register struct lhdh *lh; + + /* Allocate a LHDH device structure */ + if (nlhdhs >= LHDH_NSUP) { + fprintf(f, "Too many LHDHs, max: %d\n", LHDH_NSUP); + return NULL; + } + lh = &dvlhdh[nlhdhs++]; /* Pick unused LHDH */ + + /* Various initialization stuff */ + memset((char *)lh, 0, sizeof(*lh)); + + iodv_setnull(&lh->lh_dv); /* Init as null device */ + + lh->lh_dv.dv_init = lhdh_init; /* Set up own post-bind init */ + lh->lh_dv.dv_reset = lhdh_clear; /* System reset (clear stuff) */ + lh->lh_dv.dv_powoff = lhdh_powoff; /* Power-off cleanup */ + + /* Unibus stuff */ + lh->lh_dv.dv_pivec = lhdh_pivec; /* Return PI vector */ + lh->lh_dv.dv_read = lhdh_read; /* Read unibus register */ + lh->lh_dv.dv_write = lhdh_write; /* Write unibus register */ + + /* Configure from parsed string and remember for init + */ + if (!lhdh_conf(f, s, lh)) + return NULL; + + return &lh->lh_dv; +} + + +static dvureg_t +lhdh_pivec(register struct device *d) +{ + register struct lhdh *lh = (struct lhdh *)d; + + /* This code is peculiar since LHDH is really two devices, and + ** the output device vector setting is assumed to be 4 more than + ** that of the input device. + ** Give priority for now to input device. Later toggle. + */ + int vec = 0; + + if (lh->lh_ipireq) { + lh->lh_ipireq = 0; + vec = d->dv_brvec; + } else if (lh->lh_opireq) { + lh->lh_opireq = 0; + vec = d->dv_brvec + 4; /* Note the +4 !! */ + } + if (!lh->lh_ipireq && !lh->lh_opireq) /* Unless other dir wants PI, */ + (*d->dv_pifun)(d, 0); /* turn off interrupt request */ + + return vec; /* Return vector to use */ +} + +static int +lhdh_init(struct device *d, FILE *of) +{ + register struct lhdh *lh = (struct lhdh *)d; + + if (!imp_init(lh, of)) + return FALSE; + lh_clear(lh); + return TRUE; +} + +/* LHDH_POWOFF - Handle "power-off" which usually means the KLH10 is +** being shut down. This is important if using a dev subproc! +*/ +static void +lhdh_powoff(struct device *d) +{ + imp_kill((struct lhdh *)d); +} + + +static void +lhdh_clear(register struct device *d) +{ + lh_clear((struct lhdh *)d); +} + +/* LH_CLEAR - clear device */ +static void +lh_clear(register struct lhdh *lh) +{ + imp_stop(lh); /* Kill IMP process, ready line going down */ + + if (lh->lh_ipireq) { + lh->lh_ipireq = 0; /* Clear any interrupt request */ + (*lh->lh_dv.dv_pifun)(&lh->lh_dv, 0); + } + if (lh->lh_opireq) { + lh->lh_opireq = 0; /* Clear any interrupt request */ + (*lh->lh_dv.dv_pifun)(&lh->lh_dv, 0); + } + + lh->lh_inactf = 0; + lh->lh_outactf = 0; + + REG(lh, LHR_ICS) = LH_RDY | LH_INR; /* Control and Status, Input side */ + REG(lh, LHR_IDB) = 0; /* Data Buffer, Input */ + REG(lh, LHR_ICA) = 0; /* Current Word Address, Input */ + REG(lh, LHR_IWC) = 0; /* Word Count, Input */ + REG(lh, LHR_OCS) = LH_RDY; /* Control and Status, Output side */ + REG(lh, LHR_ODB) = 0; /* Data Buffer, Output */ + REG(lh, LHR_OCA) = 0; /* Current Word Address, Output */ + REG(lh, LHR_OWC) = 0; /* Word Count, Output */ +} + +static dvureg_t +lhdh_read(struct device *d, register uint18 addr) +{ + register struct lhdh *lh = (struct lhdh *)d; + register int reg; + + reg = (addr - lh->lh_dv.dv_addr) >> 1; + if (reg < 0 || reg >= LHR_N) { + /* In theory ought to generate illegal IO register page-fail here, + but this should never happen - all addresses for this device + are being handled. + */ + panic("lhdh_read: Unknown register %lo", (long)addr); + } + + switch (reg) { + case LHR_ICS: /* Control and Status, Input side */ + case LHR_IDB: /* Data Buffer, Input */ + case LHR_ICA: /* Current Word Address, Input */ + case LHR_IWC: /* Word Count, Input */ + case LHR_OCS: /* Control and Status, Output side */ + case LHR_ODB: /* Data Buffer, Output */ + case LHR_OCA: /* Current Word Address, Output */ + case LHR_OWC: /* Word Count, Output */ + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[LHDH RReg %#o: %#o]\r\n", + reg, (int)REG(lh, reg)); + break; + + default: + panic("lhdh_read: Unknown register %o (%lo)", reg, (long)addr); + } + return REG(lh, reg); +} + +/* Write LHDH registers. +*/ +static void +lhdh_write(struct device *d, uint18 addr, register dvureg_t val) +{ + register struct lhdh *lh = (struct lhdh *)d; + register int reg; + + reg = (addr - lh->lh_dv.dv_addr) >> 1; + if (reg < 0 || reg >= LHR_N) { + /* In theory ought to generate illegal IO register page-fail here, + but this should never happen - all addresses for this device + are being handled. + */ + panic("lhdh_write: Unknown register %lo", (long)addr); + } + + val &= MASK16; + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[LHDH WReg %#o <= %#lo]\r\n", + reg, (long)val); + switch (reg) { + case LHR_ICS: /* Control and Status, Input side */ + if (val & LH_RST) { + lh_clear(lh); + return; + } + /* Clear all but settable bits in new value, and clear those in reg */ + val &= (LH_IE|LH_A17|LH_A16|LH_RST|LH_GO|LH_SE|LH_HRC); + REG(lh, LHR_ICS) &= ~(LH_IE|LH_A17|LH_A16|LH_RST|LH_GO|LH_SE|LH_HRC + | LH_ERR|LH_NXM|LH_MRE); /* Clear err bits */ + REG(lh, LHR_ICS) |= val; /* Set register! */ + + /* Start or stop IMP process */ + if ((val & LH_HRC)==0) { /* If dropping Host Ready, */ + imp_stop(lh); /* kill IMP process */ + REG(lh, LHR_ICS) &= ~(LH_HR); /* and turn off ready lines */ + REG(lh, LHR_ICS) |= LH_INR; + lh->lh_inactf = lh->lh_outactf = FALSE; + } else { /* Host Ready turned on */ + REG(lh, LHR_ICS) |= LH_HR; + if (REG(lh, LHR_ICS)&LH_INR) { /* If IMP not already alive, */ + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[IMP: Starting...]\r\n"); + if (imp_start(lh)) { /* Start it. */ + REG(lh, LHR_ICS) &= ~LH_INR; /* Won, say IMP ready! */ + } else + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[IMP: start failed!]\r\n"); + } + } + + /* Start or stop input */ + if (REG(lh, LHR_ICS) & LH_GO) + lh_igo(lh); + else + lh->lh_inactf = FALSE; + return; + + case LHR_OCS: /* Control and Status, Output side */ + if (val & LH_RST) { + lh_clear(lh); + return; + } + val &= (LH_IE|LH_A17|LH_A16|LH_RST|LH_GO|LH_BB|LH_ELB); + REG(lh, LHR_OCS) &= + ~(LH_IE|LH_A17|LH_A16|LH_RST|LH_GO|LH_BB|LH_ELB + | LH_ERR|LH_NXM|LH_MRE); /* Clear err bits */ + REG(lh, LHR_OCS) |= val; /* Set register! */ + + /* Start or stop output */ + if (REG(lh, LHR_OCS) & LH_GO) + lh_ogo(lh); + else + lh->lh_outactf = FALSE; + return; + + case LHR_IDB: /* Data Buffer, Input */ + case LHR_ICA: /* Current Word Address, Input */ + case LHR_IWC: /* Word Count, Input */ + case LHR_ODB: /* Data Buffer, Output */ + case LHR_OCA: /* Current Word Address, Output */ + case LHR_OWC: /* Word Count, Output */ + REG(lh, reg) = val; + return; + + default: + panic("lhdh_write: Unknown register %o (%lo)", reg, (long)addr); + } +} + +/* Generate LHDH output interrupt */ + +static void +lh_oint(register struct lhdh *lh) +{ + if (REG(lh, LHR_OCS) & LH_IE) { + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[LHDH: output int]\r\n"); + lh->lh_opireq = TRUE; + (*lh->lh_dv.dv_pifun)(&lh->lh_dv, /* Put up interrupt */ + (int)lh->lh_dv.dv_brlev); + } +} + +/* Generate LHDH input interrupt */ + +static void +lh_iint(register struct lhdh *lh) +{ + if (REG(lh, LHR_ICS) & LH_IE) { + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[LHDH: input int]\r\n"); + lh->lh_ipireq = TRUE; + (*lh->lh_dv.dv_pifun)(&lh->lh_dv, /* Put up interrupt */ + (int)lh->lh_dv.dv_brlev); + } +} + +/* Activate input side - allow IMP input to be received and processed. +*/ +static void +lh_igo(register struct lhdh *lh) +{ + if (REG(lh, LHR_ICS)&LH_INR || !(REG(lh, LHR_ICS)&LH_HR)) { + REG(lh, LHR_ICS) |= LH_ERR; + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[LHDH inp err - not up]\r\n"); + lh_iint(lh); + return; + } + lh->lh_inactf = TRUE; /* OK to start reading input! */ + + if (imp_incheck(lh)) { /* Do initial check for input */ + imp_inxfer(lh); /* Have input! Go snarf it! */ + lh_idone(lh); /* Finish up LH input done */ + } else { +#if !KLH10_IMPIO_INT + /* No input now, set up to poll for input later */ + if (!lh->lh_docheck) { + lh->lh_docheck = TRUE; /* Say to check again later */ + clk_tmractiv(lh->lh_chktmr); /* Activate timer */ + } +#endif + } +} + +/* LH input done - called to finish up IMP input +*/ +static void +lh_idone(register struct lhdh *lh) +{ + REG(lh, LHR_ICS) &= ~LH_GO; /* Turn off GO bit */ + REG(lh, LHR_ICS) |= LH_EOM; /* Say End-Of-Message */ + if (REG(lh, LHR_IWC) == 0) + REG(lh, LHR_ICS) |= LH_IBF; /* Say buffer full */ + lh_iint(lh); /* Send input interrupt! */ + + lh->lh_inactf = FALSE; +#if !KLH10_IMPIO_INT + clk_tmrquiet(lh->lh_chktmr); /* Force timer to be quiescent */ + lh->lh_docheck = FALSE; +#endif +} + +static void +lh_incheck(register struct lhdh *lh) +{ + /* Verify OK to check for input */ + if (!lh->lh_inactf) + return; /* Can't input, ignore */ + + if (imp_incheck(lh)) { + imp_inxfer(lh); /* Have input! Go snarf it! */ + lh_idone(lh); /* Finish up LH input done */ + } +} + +/* Activate output side - send a message to the IMP. +** If can't do it because an outbound message is already in progress, +** complain and cause an error. +*/ +static void +lh_ogo(register struct lhdh *lh) +{ + if (REG(lh, LHR_ICS)&LH_INR || !(REG(lh, LHR_ICS)&LH_HR)) { + REG(lh, LHR_OCS) |= LH_ERR; /* IMP not up */ + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[LHDH out err - not up]\r\n"); + lh_oint(lh); + return; + } + + /* For time being, don't worry about partial transfers (sigh), + ** always assume EOM bit will be set. + */ + if (!(REG(lh, LHR_OCS)&LH_ELB)) { + fprintf(DVDBF(lh), "[LHDH out: ELB not set!]\r\n"); + } + + if (!imp_outxfer(lh)) { + REG(lh, LHR_OCS) |= LH_ERR; /* IMP not up */ + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[LHDH out err - overrun]\r\n"); + lh_oint(lh); + return; + } + /* SIMP is all done at this point. */ + /* DPIMP will call lh_evhsdon() when ready for more output. */ +} + +/* LH output done - called to finish up IMP output +*/ +static void +lh_odone(register struct lhdh *lh) +{ + REG(lh, LHR_OCS) &= ~LH_GO; /* Turn off GO bit */ + if (REG(lh, LHR_OWC) == 0) + REG(lh, LHR_OCS) |= LH_OBE; /* Say Output Buffer Empty */ + + lh_oint(lh); /* Send output interrupt! */ +} + +/* Do LHDH I/O. +** First do housekeeping to set up transfer. +** The transfer has to be broken up if the UBA map +** doesn't point to contiguous half-pages. +** +** It appears that the I/O transfer updates the following: +** WC - word count (negative) +** CA - Bus address +** +** Note that ITS always reads 256 PDP-10 words and writes in units of +** PDP-10 words. The IMP itself is limited to 8159 bits max packet size, +** which amounts to 1019 octets. The first 96 bits (12 octets) are the +** IMP header, leaving 1007 data bytes. This is rounded to PDP-10 words, +** 4 octets/word, producing 251 data words (254 words with leader). +** +*/ +static int +lh_io(register struct lhdh *lh, int wrtf) +{ + register int wcnt; + register int cnt2; + register int i, loopcnt = 0; + register h10_t map; + register paddr_t mem; + register int err = 0; + struct ubctl *ub = lh->lh_dv.dv_uba; + + if (!wrtf && !(REG(lh, LHR_ICS)&LH_SE)) { /* Crock... */ + /* If input and Store_Enable turned off, just flush data. */ + return 1; + } + + for (; wcnt = (wrtf ? REG(lh, LHR_OWC) : REG(lh, LHR_IWC)); ++loopcnt) { + + wcnt = (-(wcnt | ~MASK16))>>1; /* Find # of PDP10 words */ + if (wcnt > 01000) /* One DEC page per pass */ + wcnt = 01000; /* to simplify UBA hacking */ + + /* Determine memory address for sector */ + mem = wrtf ? REG(lh, LHR_OCA) : REG(lh, LHR_ICA); /* Bus address */ + mem |= (paddr_t)((wrtf?REG(lh, LHR_OCS):REG(lh, LHR_ICS)) + & (LH_A17|LH_A16)) << 12; /* Add extended bits */ + if (mem & ~0377774) { /* High bit or low 2 are no-nos */ + if (wrtf) REG(lh, LHR_OCS) |= LH_NXM | LH_ERR; + else REG(lh, LHR_ICS) |= LH_NXM | LH_ERR; + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[LHDH %s err - mem %#lo]\r\n", + wrtf? "out" : "in", (long)mem); + return 0; + } + mem >>= 2; + /* High 6 bits (bit 17 known clear) are index into UB map */ + map = ub->ubpmap[i = (mem>>9)]; + if (!(map & UBA_QVAL)) { /* If map entry not valid, */ + if (wrtf) REG(lh, LHR_OCS) |= LH_NXM | LH_ERR; + else REG(lh, LHR_ICS) |= LH_NXM | LH_ERR; + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[LHDH %s err - map1 %#lo]\r\n", + wrtf? "out" : "in", (long)map); + return 0; + } + mem = ((paddr_t)(map & UBA_QPAG) << 9) | (mem & 0777); /* Find phys addr */ + + /* Determine whether transfer will cross DEC page boundary. + ** If so, split into two transfers. + */ + cnt2 = wcnt; + if (((mem & 0777) + wcnt) > 01000) { + map = ub->ubpmap[++i]; /* Next map entry */ + if (i >= 64 || !(map & UBA_QVAL)) { + if (wrtf) REG(lh, LHR_OCS) |= LH_NXM | LH_ERR; + else REG(lh, LHR_ICS) |= LH_NXM | LH_ERR; + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[LHDH %s err - map2 %#lo]\r\n", + wrtf? "out" : "in", (long)map); + return 0; + } + if ((map & UBA_QPAG) != ((mem>>9)+1)) { + /* Not contiguous phys page, must split up xfer. */ + register int cnt1; + cnt1 = 01000 - (mem&0777); + err = lh_bcopy(lh, wrtf, mem, cnt1); + if (err == cnt1) { /* If won, */ + mem = (paddr_t)(map & UBA_QPAG) << 9; /* do next part */ + err = lh_bcopy(lh, wrtf, mem, cnt2 - cnt1); + if (err >= 0) err += cnt1; + } + } + } else { /* Can do single transfer */ + err = lh_bcopy(lh, wrtf, mem, cnt2); + } + + /* Success, update registers to track progress. err has + ** # of PDP10 words transferred. + */ + if (err <= 0) /* If error, */ + break; /* stop now */ + if (wrtf) { + REG(lh, LHR_OWC) = (REG(lh, LHR_OWC)+(err<<1)) & MASK16; + REG(lh, LHR_OCA) = (REG(lh, LHR_OCA)+(err<<2)) & MASK16; + } else { + REG(lh, LHR_IWC) = (REG(lh, LHR_IWC)+(err<<1)) & MASK16; + REG(lh, LHR_ICA) = (REG(lh, LHR_ICA)+(err<<2)) & MASK16; + } + if (err < cnt2) /* If transferred less than wanted, */ + break; /* means that's all we can do. */ + } + return err < 0 ? 0 : 1; /* Return success unless saw err */ +} + +static int +lh_bcopy(register struct lhdh *lh, int wrtf, paddr_t mem, register int wcnt) +{ + register w10_t w; + register unsigned char *cp; + register vmptr_t mp = vm_physmap(mem); + register int i; + + if (wrtf) { + cp = lh->lh_optr; + if (wcnt > lh->lh_owcnt) { + /* Too big for message buffer! Just discard.*/ + fprintf(DVDBF(lh), "[LHDH lhxfer output too big: %d]\r\n", wcnt); + return 0; + } + if (lh->lh_owcnt -= wcnt) /* If there'll be anything left */ + lh->lh_optr += wcnt * 4; /* update both vars */ + for (i = wcnt; --i >= 0; ++mp) { + w = vm_pget(mp); + +#if SICONF_SIMP /* Simple byte order */ + *cp++ = LHGET(w) >> 10; + *cp++ = (LHGET(w) >> 2) & 0377; + *cp++ = ((LHGET(w)&03)<<6) | (RHGET(w) >> 12); + *cp++ = (RHGET(w)>>4) & 0377; +#else /* Unibus byte order, barf */ + *cp++ = (LHGET(w) >> 2) & 0377; + *cp++ = LHGET(w) >> 10; + *cp++ = (RHGET(w)>>4) & 0377; + *cp++ = ((LHGET(w)&03)<<6) | (RHGET(w) >> 12); +#endif + } + } else { + cp = lh->lh_iptr; + if (wcnt > lh->lh_iwcnt) wcnt = lh->lh_iwcnt; + if (lh->lh_iwcnt -= wcnt) /* If there'll be anything left */ + lh->lh_iptr += wcnt * 4; /* update both vars */ + + for (i = wcnt; --i >= 0; ++mp, cp += 4) { +#if SICONF_SIMP /* Simple byte order */ + LRHSET(w, + ((uint18)cp[0]<<10) | (cp[1]<<2) | (cp[2]>>6), + ((uint18)(cp[2]&077)<<12) | (cp[3]<<4) ); +#else /* Unibus byte order, barf */ + LRHSET(w, + ((uint18)cp[1]<<10) | (cp[0]<<2) | (cp[3]>>6), + ((uint18)(cp[3]&077)<<12) | (cp[2]<<4) ); +#endif + vm_pset(mp, w); + } + } + return wcnt; +} + +/* VIRTUAL IMP ROUTINES +*/ + +/* Dummy IMP if none actually being used; pretend it's dead. + */ +#if !KLH10_DEV_SIMP && !KLH10_DEV_DPIMP + +static int imp_init(struct lhdh *lh, FILE *of) { return TRUE; } +static int imp_start(struct lhdh *lh) { return 0; } +static void imp_stop(struct lhdh *lh) { } +static void imp_kill(struct lhdh *lh) { } + +static void imp_inxfer(struct lhdh *lh) { } +static int imp_outxfer(struct lhdh *lh) { return 0; } +static int imp_incheck(struct lhdh *lh) { return 0; } + +#endif + +#if KLH10_DEV_SIMP || KLH10_DEV_DPIMP + +/* Utility routines used by both SIMP, DPIMP */ + +static void +showpkt(FILE *f, char *id, unsigned char *buf, int cnt) +{ + char linbuf[200]; + register int i; + int once = 0; + register char *cp; + + while (cnt > 0) { + cp = linbuf; + if (once++) *cp++ = '\t'; + else sprintf(cp, "%6s: ", id), cp += 8; + + for (i = 16; --i >= 0;) { + sprintf(cp, " %3o", *buf++); + cp += 4; + if (--cnt <= 0) break; + } + *cp = 0; + fprintf(f, "%s\r\n", linbuf); + } +} +#endif /* KLH10_DEV_SIMP || KLH10_DEV_DPIMP */ + + +#if KLH10_DEV_DPIMP + +static void lh_evhrwak(struct device *d, struct dvevent_s *evp); +static void lh_evhsdon(struct device *d, struct dvevent_s *evp); + +static int +imp_init(register struct lhdh *lh, FILE *of) +{ + register struct dpimp_s *dpc; + struct dvevent_s ev; + size_t junk; + + lh->lh_dpstate = FALSE; + if (!dp_init(&lh->lh_dp, sizeof(struct dpimp_s), + DP_XT_MSIG, SIGUSR1, (size_t)IMPBUFSIZ, /* in */ + DP_XT_MSIG, SIGUSR1, (size_t)IMPBUFSIZ)) { /* out */ + if (of) fprintf(of, "IMP subproc init failed!\n"); + return FALSE; + } + lh->lh_sbuf = dp_xsbuff(&(lh->lh_dp.dp_adr->dpc_todp), &junk); + lh->lh_rbuf = dp_xrbuff(&(lh->lh_dp.dp_adr->dpc_frdp), &junk); + + lh->lh_dv.dv_dpp = &(lh->lh_dp); /* Tell CPU where our DP struct is */ + + /* Set up DPIMP-specific part of shared DP memory */ + dpc = (struct dpimp_s *) lh->lh_dp.dp_adr; + dpc->dpimp_dpc.dpc_debug = lh->lh_dpdbg; /* Init DP debug flag */ + if (cpu.mm_locked) /* Lock DP mem if CPU is */ + dpc->dpimp_dpc.dpc_flags |= DPCF_MEMLOCK; + + dpc->dpimp_ver = DPIMP_VERSION; + dpc->dpimp_attrs = 0; + dpc->dpimp_blobsiz = DPIMP_BLOB_SIZE; + + dpc->dpimp_backlog = lh->lh_backlog; /* Pass on backlog value */ + dpc->dpimp_dedic = lh->lh_dedic; /* Pass on dedicated flag */ + dpc->dpimp_doarp = lh->lh_doarp; /* Pass on DOARP flag */ + dpc->dpimp_rdtmo = lh->lh_rdtmo; /* Pass on RDTMO value */ + + if (lh->lh_ifnam) /* Pass on interface name if any */ + strncpy(dpc->dpimp_ifnam, lh->lh_ifnam, sizeof(dpc->dpimp_ifnam)-1); + else + dpc->dpimp_ifnam[0] = '\0'; /* No specific interface */ + memcpy((char *)dpc->dpimp_ip, /* Set our IP address for filter */ + lh->lh_ipadr, 4); + memcpy((char *)dpc->dpimp_gw, /* Set our GW address for IMP */ + lh->lh_gwadr, 4); + memcpy(dpc->dpimp_eth, /* Set EN address if any given */ + lh->lh_ethadr, 6); /* (all zero if none) */ + + /* Register ourselves with main KLH10 loop for DP events */ + + ev.dvev_type = DVEV_DPSIG; /* Event = Device Proc signal */ + ev.dvev_arg.eva_int = SIGUSR1; + ev.dvev_arg2.eva_ip = &(lh->lh_dp.dp_adr->dpc_todp.dpx_donflg); + if (!(*lh->lh_dv.dv_evreg)((struct device *)lh, lh_evhsdon, &ev)) { + if (of) fprintf(of, "IMP event reg failed!\n"); + return FALSE; + } + + ev.dvev_type = DVEV_DPSIG; /* Event = Device Proc signal */ + ev.dvev_arg.eva_int = SIGUSR1; + ev.dvev_arg2.eva_ip = &(lh->lh_dp.dp_adr->dpc_frdp.dpx_wakflg); + if (!(*lh->lh_dv.dv_evreg)((struct device *)lh, lh_evhrwak, &ev)) { + if (of) fprintf(of, "IMP event reg failed!\n"); + return FALSE; + } + return TRUE; +} + +static int +imp_start(register struct lhdh *lh) +{ + register int res; + + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[imp_start: starting DP \"%s\"...", + lh->lh_dpname); + + /* HORRIBLE UGLY HACK: for AXP OSF/1 and perhaps other systems, + ** the virtual-runtime timer of setitimer() remains in effect even + ** for the child process of a fork()! To avoid this, we must + ** temporarily turn the timer off, then resume it after the fork + ** is safely out of the way. + ** + ** Otherise, the timer would go off and the unexpected signal would + ** chop down the DP subproc without any warning! + ** + ** Later this should be done in DPSUP.C itself, when I can figure a + ** good way to tell whether the code is part of the KLH10 or a DP + ** subproc. + */ + clk_suspend(); /* Clear internal clock if one */ + res = dp_start(&lh->lh_dp, lh->lh_dpname); + clk_resume(); /* Resume internal clock if one */ + + if (!res) { + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), " failed!]\r\n"); + else + fprintf(DVDBF(lh), "[imp_start: Start of DP \"%s\" failed!]\r\n", + lh->lh_dpname); + return FALSE; + } + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), " started!]\r\n"); + + return TRUE; +} + +/* IMP_STOP - Stops IMP and drops Host Ready by killing IMP subproc, +** but allow restarting. +*/ +static void +imp_stop(register struct lhdh *lh) +{ + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[IMP: stopping..."); + + dp_stop(&lh->lh_dp, 1); /* Say to kill and wait 1 sec for synch */ + + lh->lh_dpstate = FALSE; /* No longer there and ready */ + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), " stopped]\r\n"); +} + + +/* IMP_KILL - Kill IMP process permanently, no restart. +*/ +static void +imp_kill(register struct lhdh *lh) +{ + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[IMP kill]\r\n"); + + lh->lh_dpstate = FALSE; + (*lh->lh_dv.dv_evreg)( /* Flush all event handlers for device */ + (struct device *)lh, + NULLPROC, + (struct dvevent_s *)NULL); + dp_term(&(lh->lh_dp), 0); /* Flush all subproc overhead */ + lh->lh_sbuf = NULL; /* Clear pointers no longer meaningful */ + lh->lh_rbuf = NULL; +} + + +/* LH_EVHRWAK - Invoked by INSBRK event handling when +** signal detected from DP saying "wake up"; the DP is sending +** us an input packet. +*/ +static void +lh_evhrwak(struct device *d, struct dvevent_s *evp) +{ + register struct lhdh *lh = (struct lhdh *)d; + + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[LHDH input wakeup: %d]", + (int)dp_xrtest(dp_dpxfr(&lh->lh_dp))); + + /* Always check IMP input in order to process any non-data messages + ** regardless of whether LH is actively reading, + ** then invoke general LH check to do data transfer if OK. + */ + if (imp_incheck(lh) && lh->lh_inactf) { + imp_inxfer(lh); /* Have input! Go snarf it! */ + lh_idone(lh); /* Finish up LH input done */ + } +} + +static void +lh_evhsdon(struct device *d, struct dvevent_s *evp) +{ + register struct lhdh *lh = (struct lhdh *)d; + register struct dpx_s *dpx = dp_dpxto(&lh->lh_dp); + + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[lh_evhsdon: %d]", (int)dp_xstest(dpx)); + + if (dp_xstest(dpx)) { /* Verify message is done */ + lh_odone(lh); /* Say LH output done */ + } +} + + +/* Start IMP output. +*/ +static int +imp_outxfer(register struct lhdh *lh) +{ + register int cnt; + register struct dpx_s *dpx = dp_dpxto(&lh->lh_dp); + + /* Make sure we can output message and fail if not */ + if (!dp_xstest(dpx)) { + fprintf(DVDBF(lh), "[IMP: DP out blocked]\r\n"); + return 0; + } + + /* Output xfer requested! */ + lh->lh_owcnt = IMPBUFSIZ/4; + lh->lh_optr = lh->lh_sbuf + DPIMP_DATAOFFSET; + if (lh_io(lh, TRUE)) { /* Xfer data from mem! */ + register struct dpimp_s *dpc = (struct dpimp_s *) lh->lh_dp.dp_adr; + + cnt = ((IMPBUFSIZ/4) - lh->lh_owcnt) * 4; + if (DVDEBUG(lh) & DVDBF_DATSHO) /* Show data? */ + showpkt(DVDBF(lh), "PKTOUT", lh->lh_sbuf + DPIMP_DATAOFFSET, cnt); + + dpc->dpimp_outoff = DPIMP_DATAOFFSET; + dp_xsend(dpx, DPIMP_SPKT, (size_t)cnt + DPIMP_DATAOFFSET); + + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[IMP: Out %d]\r\n", cnt); + + } else { + /* No real transfer, but pretend completed */ + lh_odone(lh); /* Say LH output done */ + } + return 1; +} + +static int +imp_incheck(register struct lhdh *lh) +{ + register struct dpx_s *dpx = dp_dpxfr(&lh->lh_dp); + + if (dp_xrtest(dpx)) { /* Verify there's a message for us */ + switch (dp_xrcmd(dpx)) { + case DPIMP_INIT: + /* Do stuff to turn on IMP ready line? */ + dp_xrdone(dpx); /* ACK it */ + return 0; /* No actual input */ + + case DPIMP_RPKT: /* Input packet ready! */ + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[IMP: inbuf %ld]\r\n", + (long) dp_xrcnt(dpx)); + return 1; + + default: + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[IMP: R %d flushed]", dp_xrcmd(dpx)); + dp_xrdone(dpx); /* just ACK it */ + return 0; + } + } + + return 0; +} + + +/* IMP_INXFER - IMP Input. +** For time being, don't worry about partial transfers (sigh), +** which might have left part of a previous message still lying +** around waiting for the next read request. +*/ +static void +imp_inxfer(register struct lhdh *lh) +{ + register int err, cnt; + register struct dpx_s *dpx = dp_dpxfr(&lh->lh_dp); + register struct dpimp_s *dpc = (struct dpimp_s *) lh->lh_dp.dp_adr; + register unsigned char *pp; + + /* Assume this is ONLY called after verification by imp_incheck that + ** an input message is actually ready. + */ + cnt = dp_xrcnt(dpx); + + /* Adjust for possible offset */ + cnt -= dpc->dpimp_inoff; + pp = lh->lh_rbuf + dpc->dpimp_inoff; + + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[IMP: In %d]\r\n", cnt); + if (DVDEBUG(lh) & DVDBF_DATSHO) /* Show data? */ + showpkt(DVDBF(lh), "PKTIN ", pp, cnt); + + /* Message read in! Now carry out DMA xfer! */ + if (cnt & 03) { /* If msg len not multiple of 4, pad out */ + for (err = 4 - (cnt&03); --err >= 0; ++cnt) + pp[cnt] = '\0'; + } + + /* Set up args for lh_io() */ + lh->lh_iwcnt = cnt / 4; + lh->lh_iptr = pp; + lh_io(lh, 0); /* Do it, update regs */ + + dp_xrdone(dpx); /* Done, can now ACK */ +} + +#endif /* KLH10_DEV_DPIMP */ + +#if KLH10_DEV_SIMP /* Old Piped IMP stuff - kept for posterity */ + +static void imp_intmo(void *lh); +#if KLH10_IMPIO_INT +static void lh_inwak(struct device *d, struct dvevent_s *evp); +#endif + +static int +imp_init(register struct lhdh *lh, FILE *of) +{ + lh->lh_imppid = 0; + lh->lh_impi.io_fd = -1; + lh->lh_impo.io_fd = -1; + +#if KLH10_IMPIO_INT + { + struct dvevent_s ev; + + /* Register ourselves with main KLH10 loop for DP events */ + /* Note this registers for SIGURG, not the normal SIGUSR1. + ** Also, only input-ready events are signalled, not output-done + ** (which still blocks for the pipe-technique SIMP) + */ + + ev.dvev_type = DVEV_DPSIG; /* Event = Device Proc signal */ + ev.dvev_arg.eva_int = SIGURG; + ev.dvev_arg2.eva_ip = &(lh->lh_inwakflg); + if (!(*lh->lh_dv.dv_evreg)((struct device *)lh, lh_inwak, &ev)) { + if (of) fprintf(of, "LHDH event reg failed!\n"); + return FALSE; + } + lh->lh_inwakflg = TRUE; /* Reg clears, but always keep this set! */ + } +#else + /* Polling - Set up periodic input check timer */ + if (!lh->lh_chktmr) /* Check once per interval timeout */ + lh->lh_chktmr = clk_itmrget(imp_intmo, (void *)lh); + clk_tmrquiet(lh->lh_chktmr); /* Immediately make it quiescent */ + lh->lh_docheck = FALSE; +#endif + return TRUE; +} + +#if KLH10_IMPIO_INT + +/* LH_INWAK - Invoked by INSBRK event handling when +** signal detected from DP saying "wake up"; the DP is sending +** us an input packet. +*/ +static void +lh_inwak(struct device *d, struct dvevent_s *evp) +{ + register struct lhdh *lh = (struct lhdh *)d; + + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[LHDH input wakeup]\r\n"); + + lh->lh_inwakflg = TRUE; /* Always reset flag to TRUE */ + lh_incheck(lh); /* Handle any IMP input now */ +} + +#else + +static void +imp_intmo(void *lh) +{ + lh_incheck((struct lhdh *)lh); +} +#endif /* !KLH10_IMPIO_INT */ + +static int +imp_start(register struct lhdh *lh) +{ +#define PIPEIN 0 +#define PIPEOUT 1 + int fdin[2], fdout[2]; + int err; + char impaddr[32], gwaddr[32]; + int i; + char signum[32]; + char *impargs[32]; /* Should be enough args */ + + /* No-op if already set up, else verify things are consistent */ + if (lh->lh_imppid) { + if (lh->lh_impi.io_fd >= 0 && lh->lh_impo.io_fd >= 0) + return TRUE; + fprintf(DVDBF(lh),"[IMP: no IO threads??]\r\n"); + imp_stop(lh); + } else if (lh->lh_impi.io_fd >= 0 || lh->lh_impo.io_fd >= 0) { + fprintf(DVDBF(lh),"[IMP: IO but no IMP??]\r\n"); + imp_stop(lh); + } + +/* From here on, things get very system-dependent */ +#if CENV_SYS_UNIX /* Originally SUN - generic enough? */ + + /* Will need IP addresses in string form */ + sprintf(impaddr, "%d.%d.%d.%d", + lh->lh_ipadr[0], lh->lh_ipadr[1], + lh->lh_ipadr[2], lh->lh_ipadr[3]); + sprintf(gwaddr, "%d.%d.%d.%d", + lh->lh_gwadr[0], lh->lh_gwadr[1], + lh->lh_gwadr[2], lh->lh_gwadr[3]); + + /* Fire up the IMP subprocess */ + if ((err = access(lh->lh_dpname, X_OK)) < 0) { /* Check xct access */ + fprintf(DVDBF(lh), "[IMP: Cannot access \"%s\" - %s]\r\n", + lh->lh_dpname ? lh->lh_dpname : "(nullptr)", + os_strerror(err)); + return 0; + } + if ((err = pipe(fdin)) < 0) { + fprintf(DVDBF(lh), "[IMP: Cannot pipe - %s]\r\n", os_strerror(err)); + return 0; + } + if ((err = pipe(fdout)) < 0) { + fprintf(DVDBF(lh), "[IMP: Cannot pipe - %s]\r\n", os_strerror(err)); + close(fdin[0]), close(fdin[1]); + return 0; + } + + /* FIX GODDAM SUN LWP LOSSAGE!!!! + ** The NBIO library for LWPs has decided to carefully smash FD flags + ** back to their original state whenever a close() is done on them, + ** which of course affects every process using those flags. What happens + ** in a normal fork-exec sequence is that the child attempts to close + ** the unneeded pipe FDs, however the child is still running in the + ** goddam NBIO context (goddam unix fork semantics) and the flag + ** clearing done by NBIO's close in the child also zaps them for + ** the parent, which needs to keep the FASYNC and FNDELAY flags set + ** if it wants to avoid blocking! Goddam Unix FD semantics. + ** + ** For some reason, the developers forgot to cover all the loopholes + ** and a way out of this particular madness does exist. By setting + ** the F_SETFD flag on the specific doomed FDs, they can be closed + ** invisibly as a side effect of the exec() call, which NBIO doesn't + ** know about. Goddam. (Well, what do you expect after wasting + ** several more hours wallowing in yet another Unix cesspool?) + ** Goddam. + */ + +#define FIXSUNLOSSAGE 1 +#if FIXSUNLOSSAGE + /* Force the child to close these FDs as a side effect of its exec() + */ + if (fcntl(fdin[PIPEIN], F_SETFD, 1) < 0 + || fcntl(fdout[PIPEOUT], F_SETFD, 1) < 0) { + fprintf(DVDBF(lh), "[IMP: Cannot fcntl - %s]\r\n", os_strerror(errno)); + close(fdin[0]), close(fdin[1]); + close(fdout[0]), close(fdout[1]); + return 0; + } +#endif + if ((lh->lh_imppid = fork()) < 0) { + lh->lh_imppid = 0; + fprintf(DVDBF(lh), "[IMP: Cannot fork - %s]\r\n", os_strerror(errno)); + close(fdin[0]), close(fdin[1]); + close(fdout[0]), close(fdout[1]); + return 0; + } + if (lh->lh_imppid == 0) { /* We're the child? */ + /* Child process code */ +#if !FIXSUNLOSSAGE /* See note above */ + close(fdin[PIPEIN]); + close(fdout[PIPEOUT]); +#endif + if (dup2(fdin[PIPEOUT], 1) != 1 + || dup2(fdout[PIPEIN], 0) != 0) { + fprintf(DVDBF(lh), "[IMP: Cannot dup2 - %s]\r\n", + os_strerror(errno)); + exit(1); + } + + /* Set up args for IMP sub-process */ + impargs[i = 0] = "SIMP"; + impargs[++i] = impaddr; /* KLH10 address */ + impargs[++i] = "-g"; /* Prime GW address */ + impargs[++i] = gwaddr; + if (lh->lh_dpdbg) + impargs[++i] = "-d"; /* Debug flag */ + if (lh->lh_ifnam) { + impargs[++i] = "-i"; /* Interface spec */ + impargs[++i] = lh->lh_ifnam; + } +#if KLH10_IMPIO_INT + sprintf(signum, "%d", SIGURG); + impargs[++i] = "-s"; /* Send SIGURG when input ready */ + impargs[++i] = signum; +#endif + impargs[++i] = NULL; /* Tie off arg list */ + + execv(lh->lh_dpname, impargs); + + fprintf(DVDBF(lh), "[IMP: execv failed - %s]\r\n", + os_strerror(errno)); + exit(1); + } else { + close(fdin[PIPEOUT]); /* Parent, flush unused FDs */ + close(fdout[PIPEIN]); + } +#endif /* CENV_SYS_UNIX */ + + /* Now finalize two independent threads to do I/O for it */ + lh->lh_impi.io_fd = fdin[PIPEIN]; + lh->lh_impo.io_fd = fdout[PIPEOUT]; + + return TRUE; +} + + + +/* IMP_STOP - Carry out dropping of Host Ready by killing IMP process. +** The wait() call will be a problem as soon as more devices are +** emulated by subprocesses (eg magtape). +*/ +static void +imp_stop(register struct lhdh *lh) +{ + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[IMP stop - %d]\r\n", lh->lh_imppid); + + if (lh->lh_imppid) { + int status; + kill(lh->lh_imppid, SIGKILL); + wait(&status); + lh->lh_imppid = 0; + } + if (lh->lh_impi.io_fd >= 0) { + close(lh->lh_impi.io_fd); + lh->lh_impi.io_fd = -1; + } + if (lh->lh_impo.io_fd >= 0) { + close(lh->lh_impo.io_fd); + lh->lh_impo.io_fd = -1; + } +} + +/* IMP_KILL - Permanent kill, no restart +*/ +static void +imp_kill(register struct lhdh *lh) +{ + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[IMP kill - %d]\r\n", lh->lh_imppid); + + (*lh->lh_dv.dv_evreg)( /* Flush all event handlers for device */ + (struct device *)lh, + (void (*)())NULL, + (struct dvevent_s *)NULL); + imp_stop(lh); +} + + +static int +imp_incheck(register struct lhdh *lh) +{ + /* OSD WARNING: the FIONREAD ioctl is defined to want a "long" on SunOS + ** and presumably old BSD, but it uses an "int" on Solaris and DEC OSF/1! + ** Leave undefined for unknown systems to ensure this is checked for + ** each new port. + */ +#if CENV_SYS_SUN + long retval; +#elif CENV_SYS_SOLARIS || CENV_SYS_DECOSF || CENV_SYS_XBSD || CENV_SYS_LINUX + int retval; +#endif + + if (ioctl(lh->lh_impi.io_fd, FIONREAD, &retval) /* If call fails, */ + || !retval) /* or if returned zilch */ + return 0; /* assume no input waiting */ + + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[IMP: inpipe %ld]\r\n", (long)retval); + + return (int) retval; +} + + +/* IMP_INXFER - IMP Input loop. +*/ +static int fullread(int fd, unsigned char *buf, int cnt); + +static void +imp_inxfer(register struct lhdh *lh) +{ + register int err, cnt; + register unsigned char *buf = lh->lh_impi.io_buf; + int fd = lh->lh_impi.io_fd; + + /* Input xfer requested! */ + /* For time being, don't worry about partial transfers (sigh), + ** which might have left part of a previous message still lying + ** around waiting for the next read request. + */ + if (err = fullread(fd, buf, SIH_HSIZ)) { + fprintf(DVDBF(lh), "[IMP: hdr read failed - %s]\r\n", + os_strerror(err)); + return; + } + if (buf[0] != SIH_HDR || buf[1] != SIH_TDATA) { + fprintf(DVDBF(lh), "[IMP: bad header - %#o]\r\n", buf[0]); + /* Perhaps attempt to get back in synch */ + return; + } + cnt = ((buf[2]&0377)<<8) + (buf[3]&0377); + if (cnt >= IMPBUFSIZ) { + fprintf(DVDBF(lh), "[IMP: count too big - %d]\r\n", cnt); + /* Perhaps attempt to get back in synch */ + return; + } + if (err = fullread(fd, buf, cnt)) { + fprintf(DVDBF(lh), "[IMP: read failed - %s]\r\n", + os_strerror(err)); + return; + } + + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[IMP: In %d]\r\n", cnt); + if (DVDEBUG(lh) & DVDBF_DATSHO) /* Show data? */ + showpkt(DVDBF(lh), "PKTIN ", buf, cnt); + + + /* Message read in! Now carry out DMA xfer! */ + if (cnt & 03) { /* If msg len not multiple of 4, pad out */ + for (err = 4 - (cnt&03); --err >= 0; ++cnt) + buf[cnt] = '\0'; + } + + /* Set up args for lh_io() */ + lh->lh_iwcnt = cnt / 4; + lh->lh_iptr = buf; + lh_io(lh, 0); /* Do it, update regs */ +} + +static int +fullread(int fd, unsigned char *buf, int cnt) +{ + register int err; + + while ((err = read(fd, buf, cnt)) != cnt) { + if (err <= 0) { + return err ? errno : EPIPE; /* Sigh, hack hack */ + } + cnt -= err; + buf += err; + } + return 0; /* Counted out, won */ +} + + +static int +imp_outxfer(register struct lhdh *lh) +{ + register int err, cnt; + register unsigned char *buf = lh->lh_impo.io_buf; + int fd = lh->lh_impo.io_fd; + + /* Output xfer requested! */ + lh->lh_owcnt = (IMPBUFSIZ - SIH_HSIZ)/4; + lh->lh_optr = buf + SIH_HSIZ; + if (lh_io(lh, TRUE)) { /* Xfer data from mem! */ + cnt = (((IMPBUFSIZ - SIH_HSIZ)/4) - lh->lh_owcnt) * 4; + buf[0] = SIH_HDR; + buf[1] = SIH_TDATA; + buf[2] = (cnt >> 8) & 0377; + buf[3] = cnt & 0377; + if (DVDEBUG(lh) & DVDBF_DATSHO) /* Show data? */ + showpkt(DVDBF(lh), "PKTOUT", buf+4, cnt); + err = write(fd, buf, cnt + 4); + if (DVDEBUG(lh)) + fprintf(DVDBF(lh), "[IMP: Out %d]\r\n", err); + + if (err != (cnt+4)) { + if (err < 0) + fprintf(DVDBF(lh), "[imp_outxfer: write failed - %s]\r\n", + os_strerror(err)); + } + } + + lh_odone(lh); /* Say LH output done */ + return 1; +} +#endif /* KLH10_DEV_SIMP */ + +#endif /* KLH10_DEV_LHDH */ diff --git a/src/dvlhdh.h b/src/dvlhdh.h new file mode 100644 index 0000000..43e5020 --- /dev/null +++ b/src/dvlhdh.h @@ -0,0 +1,104 @@ +/* DVLHDH.H - ACC LH-DH IMP Interface definitions +*/ +/* $Id: dvlhdh.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dvlhdh.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ +/* +** Portions of this file were derived from AI:SYSTEM;LHDH DEFS5 +*/ + +#ifndef DVLHDH_INCLUDED +#define DVLHDH_INCLUDED 1 + +#ifdef RCSID + RCSID(dvlhdh_h,"$Id: dvlhdh.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* +; The ACC IMP interface on the KS implements two separate Unibus IO +; devices, for input and output. Both of these devices transfer data in +; 32-bit mode only over the Unibus via DMA. Because of the DMA data +; transfer the interrupt structure is trivial, and uses NETCHN only. +; +; Apparently the reset bits in the two CSRs are wired together, so +; resetting one side of the machine resets both. This action also drops +; the HOST READY line to the IMP. +; +; You must set %LHSE whenever the HOST READY line is high, or the IMP +; will be allowed to freely throw away data. This is true across IMP +; message boundaries, and even if no input request is active. +; +*/ + +extern struct device *dvlhdh_create(FILE *f, char *s); + + +/* ACC LH-DH IMP Interface Bits. */ + +#if 0 /* Default BR, VEC, ADDR settings for AI ITS */ + /* On Unibus #3: */ +#define UB_LHDH_BR 6 /* Interrupts occur on level 6 (non-std) */ +#define UB_LHDH_IVEC 0250 /* Input side interrupt vector (non-std) */ +#define UB_LHDH_OVEC 0254 /* Output side assumed to be LH_IVEC+4 */ +#define UB_LHDH 0767600 /* Base of LH/DH Unibus reg address space */ +#endif + +/* LHDH Registers - 16-bit word offsets from base address */ +#define LHR_ICS 0 /* Input Control and Status */ +#define LHR_IDB 1 /* Input Data Buffer */ +#define LHR_ICA 2 /* Input Current Word Address */ +#define LHR_IWC 3 /* Input Word Count */ +#define LHR_OCS 4 /* Output Control and Status */ +#define LHR_ODB 5 /* Output Data Buffer */ +#define LHR_OCA 6 /* Output Current Word Address */ +#define LHR_OWC 7 /* Output Word Count */ +#define LHR_N 8 /* # of registers */ + + +/* Bits in CSRs */ + +/* Bits common to input and output */ +#define LH_ERR (1<<15) /* Error present */ +#define LH_NXM (1<<14) /* Non Existant Memory on DMA */ +#define LH_MRE (1<<9) /* Master Ready Error (ready bounce during xfr) */ +#define LH_RDY (1<<7) /* Device Ready (modifying LHDH regs allowed) */ +#define LH_IE (1<<6) /* Interrupt Enable */ +#define LH_A17 (1<<5) /* Address bit 17 for extended unibus xfrs */ +#define LH_A16 (1<<4) /* Address bit 16 for extended unibus xfrs */ +#define LH_RST (1<<1) /* Interface Reset */ +#define LH_GO 01 /* GO - Start DMA Transfer */ + +/* Input side */ +#define LH_EOM (1<<13) /* End-of-Message received from IMP */ +#define LH_HR (1<<11) /* Host Ready (ACC's relay closed, debounced) */ +#define LH_INR (1<<10) /* IMP not ready */ +#define LH_IBF (1<<8) /* Input Buffer Full */ +#define LH_SE (1<<3) /* Store Enable (0 == flush data instead) */ +#define LH_HRC (1<<2) /* Host Ready Relay Control (1 to close relay) */ + +/* Output side */ +#define LH_WC0 (1<<13) /* Output Word Count is zero */ +#define LH_OBE (1<<8) /* Output Buffer Empty */ +#define LH_BB (1<<3) /* Bus Back (loopback enable for testing) */ +#define LH_ELB (1<<2) /* Send EOM indication to IMP at end of xfr */ + /* (enable Last Bit Flag) */ + +#endif /* ifndef DVLHDH_INCLUDED */ diff --git a/src/dvni20.c b/src/dvni20.c new file mode 100644 index 0000000..6eb92ee --- /dev/null +++ b/src/dvni20.c @@ -0,0 +1,3498 @@ +/* DVNI20.C - Emulates NIA20 Network Interface for KL10 +*/ +/* $Id: dvni20.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1994, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dvni20.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* + This implements a new device subprocess (DP) mechanism. The +correspondence between the NIA20 state and the DP state is: + + NIA20 DP + ----- -- + dvni20 setup dpni20 setup (mem seg ready), no fork. + Not running No fork/program. + Running DP fork/program running! + Disabled NI20 code flushes DP input, does no output. + Enabled NI20 code reads DP input & does output. +*/ +#include "klh10.h" + +#if !KLH10_DEV_NI20 && CENV_SYS_DECOSF + /* Stupid gubbish needed to prevent OSF/1 AXP compiler from + ** halting merely because compiled file is empty! + */ +static int decosfcclossage; +#endif + +#if KLH10_DEV_NI20 /* Moby conditional for entire file */ + +#include /* For size_t etc */ +#include +#include +#include +#include /* For malloc */ + +#include "kn10def.h" /* This includes OSD defs */ +#include "kn10dev.h" +#include "kn10ops.h" +#include "dvni20.h" +#include "prmstr.h" /* For parameter parsing */ + +#if KLH10_DEV_DPNI20 /* Event handling and dev sub-proc stuff! */ +# include "dpni20.h" +#endif + +#ifdef RCSID + RCSID(dvni20_c,"$Id: dvni20.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Ethernet packet definitions */ + +#define ETHER_ADRSIZ 6 /* # bytes in ethernet address */ +#define ETHER_HDRSIZ 14 /* # bytes in header (2 addrs plus type) */ +#define ETHER_MTU 1500 /* Max # data bytes in ethernet pkt */ +#define ETHER_MIN (60-ETHER_HDRSIZ) /* Minimum # data bytes */ +#define ETHER_CRCSIZ 4 /* # bytes in trailing CRC */ + + /* Ethernet packet offset values */ +#define ETHER_PX_DST 0 /* Dest address */ +#define ETHER_PX_SRC 6 /* Source address */ +#define ETHER_PX_TYP 12 /* Type (high byte first) */ +#define ETHER_PX_DAT 14 /* Data bytes */ + /* CRC comes after data, which is variable-length */ + +#define NI20_MAXPKTLEN 1520 /* # bytes in max pkt NIA20 handles */ + /* (actually 1519 but round up) */ + +/* Later move these elsewhere? */ +#define w10topa(w) ((paddr_t)W10_U32(w) & MASK22) +#define patow10(w,pa) W10_U32SET((w), (pa)) + +/* Likewise move to dvdchn.h or something */ +struct dvdchn { + int dc_eptoff; /* Offset of channel area in EPT */ + paddr_t dc_clp; /* DC "PC" - Current Command List Pointer */ + uint18 dc_sts; /* DC status flag bits (LH) */ + w10_t dc_ccw; /* DC current cmd word */ + int dc_wcnt; /* DC cmd word count */ + paddr_t dc_buf; /* DC cmd data buffer pointer */ + int dc_hlt; /* TRUE if halt when current wordcount done */ + int dc_rev; /* TRUE if doing reverse data xfer */ + int dc_wrt; /* TRUE if doing device write */ +}; + +void dchn_init(struct dvdchn *dc, int mbcn); +void dchn_clear(struct dvdchn *dc); +int dchn_ccwget(struct dvdchn *dc); + + +/* Internal PTT entry */ +struct niptt { + unsigned int ptt_type; /* 16-bit protocol type */ + paddr_t ptt_freeq; /* Phys addr of free queue header */ +}; + +/* Internal echo check buffer entry */ +struct niecbe { + int niec_deathtmo; /* .5-sec ticks left to live */ + unsigned char niec_hdr[ETHER_HDRSIZ]; /* Ether header */ + uint32 niec_digest; /* Checksum/hash of data */ +}; + +struct ni20 { + struct device ni_dv; /* Generic 10 device structure */ + + /* NI20-specific vars */ + int ni_no; /* NI20 Unit number (0-7) (normally 5) */ + uint18 ni_lhcond; /* LH CONI bits (CSR) */ + uint18 ni_cond; /* RH CONI bits (CSR) */ + int ni_pia; /* PIA from CSR bits (vs ni_ppia, from PCB) */ + int ni_pilev; /* PI level mask (0 if PIA=0) */ + int ni_pivec; /* PI vector for RH of PI function word */ + + int ni_state; /* NI port state */ +# define NI20_STF_RUN 02 /* Running */ +# define NI20_STF_ENA 01 /* If Running, 1=Enabled/0=Disabled */ + /* else 1=Halt/0=Uninitialized */ +# define NI20_ST_UNINT 0 /* Uninitialized (powerup, not running) */ +# define NI20_ST_HALT 1 /* Halted (normally from error) */ +# define NI20_ST_RUN 2 /* Running disabled */ +# define NI20_ST_RUNENA 3 /* Running enabled */ + int ni_rar; /* RAR (RAM Address Register) 13 bits + 1 LH/RH bit */ + int ni_lar; /* LAR (Last Address Register) 13 bits */ + w10_t ni_ebuf; /* Ebuf word */ + + /* Station info */ + unsigned char ni_ethadr[6]; /* Ethernet addr of this NIA20 port */ + dw10_t ni_dwethadr; /* Ethernet addr in PDP-10 format */ + int ni_staflgs; /* Station flags (NI20_RSF_*) */ + int ni_retries; /* # retries allowed (default 5, T20 sets to 16) */ + + /* Misc config info not set elsewhere */ + char *ni_ifnam; /* Native platform's interface name */ + int ni_dedic; /* TRUE if interface dedicated (else shared) */ + int ni_decnet; /* TRUE to filter DECNET packets (if shared) */ + int ni_doarp; /* TRUE to do ARP hackery (if shared) */ + int ni_backlog; /* Max # input msgs to queue up in kernel */ + int ni_rdtmo; /* # secs to timeout on packetfilter reads */ + int ni_lsapf; /* TRUE to filter on LSAP addr (if shared) */ + int ni_lsap; /* LSAP src/dst address for above */ + unsigned char ni_ipadr[4]; /* KLH10 IP address to filter (if shared) */ + int32 ni_c3dly; /* Initial-cmd delay in ticks */ + int32 ni_c3dlyct; /* Countdown (no delay if 0) */ + + /* Cached port control block data */ + paddr_t ni_pcba; /* Phys addr of PCB */ + vmptr_t ni_pcbvp; /* Ptr to actual PCB memory */ + int ni_ppia; /* Port PIA */ + w10_t ni_ivec; /* Port interrupt vector */ + int ni_upqelen; /* Unknown Protocol Queue Entry length, in words */ + + /* Semi-cached PCB data - PCB entry is re-checked on appropriate cmd */ + vmptr_t ni_pttvp; /* Ptr to PTT in 10 memory; updated by LDPTT */ + vmptr_t ni_mcatvp; /* Ptr to MCAT in 10 memory; updated by LDMCAT */ + vmptr_t ni_rcbvp; /* Ptr to counters in 10 memory; updated by RCCNT */ + + struct dvdchn ni_dc; /* Data Channel vars */ + + int ni_istate; /* Internal state for timeouts etc */ + int ni_cmdqf; /* TRUE if want to check cmd queue */ + paddr_t ni_qepa; /* Active queue entry phys addr (needs relinking) */ + paddr_t ni_qhpa; /* Active queue header phys addr (relink here) */ + int ni_pktinf; /* TRUE if have packet input waiting */ + + int ni_nptts; /* # of valid entries in PTT */ + int ni_nmcats; /* # of valid entries in MCAT */ + struct niptt ni_ptt[NI20_NPTT]; /* Internal PTT */ + dw10_t ni_mcat[NI20_NMTT]; /* Internal MCAT - simple addr table */ + + uint32 ni_cnts[NI20_RCLEN]; + +#if KLH10_DEV_DPNI20 + int ni_dpstate; /* TRUE if dev process has finished its init */ + struct dp_s ni_dp; /* Handle on dev process */ + char *ni_dpname; /* Pointer to dev process pathname */ + unsigned char *ni_sbuf; /* Pointers to shared memory buffers */ + unsigned char *ni_rbuf; + int ni_rcnt; /* # chars in received packet input buffer */ + int ni_dpidly; /* # secs to sleep when starting NI DP */ + int ni_dpdbg; /* Initial DP debug flag */ +#endif + + /* New clock timer stuff */ + struct clkent *ni_chktmr; /* Timer for periodic run re-checks */ + int ni_docheck; /* TRUE if timer active */ + + /* Ugly echo packet checking, to emulate NI's half-duplex lossage */ + int ni_ecchk; /* TRUE to check for (and flush) echoed packets */ + int ni_ecblen; /* Echo check buffer length (# entries) */ + int ni_ectmo; /* Echo check packet timeout (# half-secs) */ + struct niecbe *ni_ecb; /* Echo check ring buffer */ + int ni_ecbfuse; /* Index of first active entry */ + int ni_ecbffree; /* Index of first free entry */ + struct clkent *ni_ectmr; /* Timer for periodic buffer reaping */ + int ni_ectact; /* TRUE if timer active */ +}; + +static int nni20s = 0; +/* static */ struct ni20 dvni20[NI20_NSUP]; + + +static unsigned char ni20_bcadr[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; +static dw10_t ni20_dwbcadr; + +#define NIDEBUG(ni) ((ni)->ni_dv.dv_debug) +#define NIDBF(ni) ((ni)->ni_dv.dv_dbf) + + +/* Function predecls */ + +/* Functions provided to device vector */ + +static w10_t ni20_pifnwd(struct device *d); +static void ni20_cono(struct device *d, h10_t erh); +static w10_t ni20_coni(struct device *d); +static void ni20_datao(struct device *d, w10_t w); +static w10_t ni20_datai(struct device *d); +static int ni20_init(struct device *d, FILE *of); +static void ni20_reset(struct device *d); +static void ni20_powoff(struct device *d); +#if KLH10_DEV_DPNI20 +static void ni20_evhsdon(struct device *d, struct dvevent_s *evp); +static void ni20_evhrwak(struct device *d, struct dvevent_s *evp); +#endif + +/* Completely internal functions */ + +static int ni20_conf(FILE *f, char *s, struct ni20 *ni); +static void ni20_clear(struct ni20 *ni); +static void ni20_picheck(struct ni20 *ni); +static void ni20_pi(struct ni20 *ni); +static void ni20_piclr(struct ni20 *ni); +static void ni20_start(struct ni20 *ni); +static void ni20_stop(struct ni20 *ni); +static void ni20_enable(struct ni20 *ni); +static void ni20_disable(struct ni20 *ni); +static void ni20_run(struct ni20 *ni); +static int ni20_runclk(void *arg); +static void ni_ethtodw(dw10_t *da, unsigned char *ea); +static int ni20_cmdchk(struct ni20 *ni); +#if KLH10_DEV_DPNI20 +static void ni20_iniable(register struct ni20 *ni); +#endif + +static int + nicmd_snddg(struct ni20 *ni, vmptr_t qep), + nicmd_ldmcat(struct ni20 *ni, vmptr_t qep), + nicmd_ldptt(struct ni20 *ni, vmptr_t qep), + nicmd_rccnt(struct ni20 *ni, vmptr_t qep), + nicmd_wrtpli(struct ni20 *ni, vmptr_t qep), + nicmd_rdpli(struct ni20 *ni, vmptr_t qep), + nicmd_rdnsa(struct ni20 *ni, vmptr_t qep), + nicmd_wrtnsa(struct ni20 *ni, vmptr_t qep), + nicmd_dgrcv(struct ni20 *ni, unsigned char *ucp, unsigned int blen); + +static void ni20_ldptt(struct ni20 *ni); +static void ni20_ldmcat(struct ni20 *ni); +static paddr_t ni_pttfind(struct ni20 *ni, unsigned int ptyp); +static int ni_ipttfind(struct ni20 *ni, unsigned int ptyp); +static int ni_qeput(struct ni20 *ni, unsigned int qh, unsigned int qe); +static paddr_t ni_qeget(struct ni20 *ni, unsigned int qh); +static int ni_qeunget(struct ni20 *ni, unsigned int qh, unsigned int qe); +static int ni_qecnt(struct ni20 *ni, unsigned int qh); + +static int ni20_ecpclk(void *arg); +static void ni_ecpstore(struct ni20 *ni, unsigned char *ucp, unsigned int len); +static int ni_ecpcheck(struct ni20 *ni, unsigned char *ucp, unsigned int len); +static uint32 ni_ecpdigest(unsigned char *ucp, int len); + +/* Configuration Parameters */ + +#define DVNI20_PARAMS \ + prmdef(NIP_DBG,"debug"), /* Initial debug flag */\ + prmdef(NIP_EN, "enaddr"), /* Ethernet address to use (override) */\ + prmdef(NIP_IFC,"ifc"), /* Ethernet interface name */\ + prmdef(NIP_BKL,"backlog"),/* Max bklog for rcvd pkts (else sys default) */\ + prmdef(NIP_DED,"dedic"), /* TRUE= Ifc dedicated (else shared) */\ + prmdef(NIP_IP, "ipaddr"), /* IP address of KLH10, if shared */\ + prmdef(NIP_DEC,"decnet"), /* TRUE= if shared, seize DECNET pkts */\ + prmdef(NIP_ARP,"doarp"), /* TRUE= if shared, do ARP hackery */\ + prmdef(NIP_LSAP,"lsap"), /* Set= if shared, filter on LSAP pkts */\ + prmdef(NIP_ECCHK,"echochk"), /* TRUE= check for echoed pkts */\ + prmdef(NIP_ECBUF,"echobuf"), /* # echoed pkts to remember */\ + prmdef(NIP_ECTMO,"echotmo"), /* # secs to remember them */\ + prmdef(NIP_C3DLY,"c3dly"), /* # ticks to use for NI cmd #3 (LDPTT)*/\ + prmdef(NIP_RDTMO,"rdtmo"), /* # secs to timeout on packetfilter read */\ + prmdef(NIP_DPDLY,"dpdelay"),/* # secs to sleep when starting DP */\ + prmdef(NIP_DPDBG,"dpdebug"),/* Initial DP debug value */\ + prmdef(NIP_DP, "dppath") /* Device subproc pathname */ + +enum { +# define prmdef(i,s) i + DVNI20_PARAMS +# undef prmdef +}; + +static char *niprmtab[] = { +# define prmdef(i,s) s + DVNI20_PARAMS +# undef prmdef + , NULL +}; + + /* Local parsing routines */ +static int pareth(char *cp, unsigned char *adr); +static int parip(char *cp, unsigned char *adr); + +/* NI20_CONF - Parse configuration string and set defaults. +** At this point, device has just been created, but not yet bound +** or initialized. +** NOTE that some strings are dynamically allocated! Someday may want +** to clean them up nicely if config fails or device is uncreated. +*/ + +static int +ni20_conf(FILE *f, char *s, struct ni20 *ni) +{ + int i, ret = TRUE; + struct prmstate_s prm; + char buff[200]; + long lval; + + /* First set defaults for all configurable parameters */ + DVDEBUG(ni) = FALSE; + ni->ni_ifnam = NULL; + ni->ni_backlog = 0; + ni->ni_decnet = FALSE; + ni->ni_dedic = FALSE; + ni->ni_doarp = TRUE; + ni->ni_lsapf = FALSE; + ni->ni_lsap = 0; + ni->ni_ecchk = -1; /* Initially unknown */ + ni->ni_ecblen = 60; + ni->ni_ectmo = 1*2; + ni->ni_rdtmo = 0; + ni->ni_c3dly = 5; /* Conservative 5-millisec timeout for T10 */ + ni->ni_c3dlyct = 0; +#if KLH10_DEV_DPNI20 + ni->ni_dpname = "dpni20"; /* Pathname of device subproc */ + ni->ni_dpidly = 5; /* Conservative 5-second timeout for T10/T20 */ + ni->ni_dpdbg = FALSE; +#endif + + prm_init(&prm, buff, sizeof(buff), + s, strlen(s), + niprmtab, sizeof(niprmtab[0])); + while ((i = prm_next(&prm)) != PRMK_DONE) { + switch (i) { + case PRMK_NONE: + fprintf(f, "Unknown NI20 parameter \"%s\"\n", prm.prm_name); + ret = FALSE; + continue; + case PRMK_AMBI: + fprintf(f, "Ambiguous NI20 parameter \"%s\"\n", prm.prm_name); + ret = FALSE; + continue; + default: /* Handle matches not supported */ + fprintf(f, "Unsupported NI20 parameter \"%s\"\n", prm.prm_name); + ret = FALSE; + continue; + + case NIP_DBG: /* Parse as true/false boolean or number */ + if (!prm.prm_val) /* No arg => default to 1 */ + DVDEBUG(ni) = 1; + else if (!s_tobool(prm.prm_val, &DVDEBUG(ni))) + break; + continue; + + case NIP_IP: /* Parse as IP address: u.u.u.u */ + if (!prm.prm_val) + break; + if (!parip(prm.prm_val, &ni->ni_ipadr[0])) + break; + continue; + + case NIP_EN: /* Parse as EN address in hex */ + if (!prm.prm_val) + break; + if (!pareth(prm.prm_val, &ni->ni_ethadr[0])) + break; + continue; + + case NIP_IFC: /* Parse as simple string */ + if (!prm.prm_val) + break; + ni->ni_ifnam = s_dup(prm.prm_val); + continue; + + case NIP_BKL: /* Parse as decimal number */ + if (!prm.prm_val || !s_todnum(prm.prm_val, &lval)) + break; + ni->ni_backlog = lval; + continue; + + case NIP_DED: /* Parse as true/false boolean */ + if (!prm.prm_val) + break; + if (!s_tobool(prm.prm_val, &ni->ni_dedic)) + break; + continue; + + case NIP_DEC: /* Parse as true/false boolean */ + if (!prm.prm_val) + break; + if (!s_tobool(prm.prm_val, &ni->ni_decnet)) + break; + continue; + + case NIP_ARP: /* Parse as true/false boolean or number */ + if (!prm.prm_val) + break; + if (!s_tobool(prm.prm_val, &ni->ni_doarp)) + break; + continue; + + case NIP_LSAP: /* Parse as number, preferably hex */ + if (!prm.prm_val || !s_todnum(prm.prm_val, &lval)) + break; + if (lval <= 0xFF) { /* If only one LSAP byte set, */ + lval |= (lval << 8); /* double it up for both dest & src */ + } + ni->ni_lsap = lval; + ni->ni_lsapf = TRUE; + continue; + + case NIP_RDTMO: /* Parse as decimal number */ + if (!prm.prm_val || !s_todnum(prm.prm_val, &lval)) + break; + ni->ni_rdtmo = lval; + continue; + + case NIP_C3DLY: /* Parse as decimal number */ + if (!prm.prm_val || !s_todnum(prm.prm_val, &lval)) + break; + ni->ni_c3dly = lval; + continue; + + case NIP_ECCHK: /* Parse as true/false boolean */ + if (!prm.prm_val) + break; + if (!s_tobool(prm.prm_val, &ni->ni_ecchk)) + break; + continue; + + case NIP_ECBUF: /* Parse as decimal number */ + if (!prm.prm_val || !s_todnum(prm.prm_val, &lval)) + break; + ni->ni_ecblen = lval; + continue; + + case NIP_ECTMO: /* Parse as decimal number */ + if (!prm.prm_val || !s_todnum(prm.prm_val, &lval)) + break; + ni->ni_ectmo = lval * 2; /* Half-secs! */ + continue; + + case NIP_DPDLY: /* Parse as decimal number */ +#if KLH10_DEV_DPNI20 + if (!prm.prm_val || !s_todnum(prm.prm_val, &lval)) + break; + ni->ni_dpidly = lval; +#endif + continue; + + case NIP_DPDBG: /* Parse as true/false boolean or number */ +#if KLH10_DEV_DPNI20 + if (!prm.prm_val) /* No arg => default to 1 */ + ni->ni_dpdbg = 1; + else if (!s_tobool(prm.prm_val, &(ni->ni_dpdbg))) + break; +#endif + continue; + + case NIP_DP: /* Parse as simple string */ +#if KLH10_DEV_DPNI20 + if (!prm.prm_val) + break; + ni->ni_dpname = s_dup(prm.prm_val); +#endif + continue; + } + ret = FALSE; + fprintf(f, "NI20 param \"%s\": ", prm.prm_name); + if (prm.prm_val) + fprintf(f, "bad value syntax: \"%s\"\n", prm.prm_val); + else + fprintf(f, "missing value\n"); + } + + /* Param string all done, do followup checks */ + + /* Unless interface is dedicated, either DECNET or IPADDR *must* be set! */ + if (!ni->ni_dedic + && !ni->ni_decnet + && (memcmp(ni->ni_ipadr, "\0\0\0\0", 4) == 0)) { + fprintf(f, + "NI20 param \"decnet\" or \"ipaddr\" must be set for a shared interface\n"); + return FALSE; + } + + /* If necessary, make a guess as to whether to do echo checking. Although + ** setting it TRUE is the safe default for accurate emulation, the overhead + ** may sometimes be questionable. + */ + if (ni->ni_ecchk == -1) { /* If no explicit setting */ + if (ni->ni_dedic /* Fast if dedicated, so OK */ + || ni->ni_decnet) /* Shared DECNET must play safe */ + ni->ni_ecchk = TRUE; + else + ni->ni_ecchk = FALSE; /* Shared IP uses OS filtering */ + } + + return ret; +} + +static int +parip(char *cp, unsigned char *adr) +{ + unsigned int b1, b2, b3, b4; + + if (4 != sscanf(cp, "%u.%u.%u.%u", &b1, &b2, &b3, &b4)) + return FALSE; + if (b1 > 255 || b2 > 255 || b3 > 255 || b4 > 255) + return FALSE; + *adr++ = b1; + *adr++ = b2; + *adr++ = b3; + *adr = b4; + return TRUE; +} + +static int +pareth(char *cp, unsigned char *adr) +{ + unsigned int b1, b2, b3, b4, b5, b6; + int cnt; + + cnt = sscanf(cp, "%x:%x:%x:%x:%x:%x", &b1, &b2, &b3, &b4, &b5, &b6); + if (cnt != 6) { + /* Later try as single large address #? */ + return FALSE; + } + if (b1 > 255 || b2 > 255 || b3 > 255 || b4 > 255 || b5 > 255 || b6 > 255) + return FALSE; + *adr++ = b1; + *adr++ = b2; + *adr++ = b3; + *adr++ = b4; + *adr++ = b5; + *adr = b6; + return TRUE; +} + +/* NI20 interface routines to KLH10 */ + +/* Address note: It's unclear where ni_ethadr should be initialized. +** It could be obtained from the host platform's OS, or could +** be specified in the device config param string. +** +** For now, init it with a compile-time param, sigh. +*/ + +struct device * +dvni20_create(FILE *f, char *s) +{ + register struct ni20 *ni; + + /* Parse string to determine which device to use, config, etc etc + ** But for now, just allocate sequentially. Hack. + */ + if (nni20s >= NI20_NSUP) { + fprintf(f, "Too many NI20s, max: %d\n", NI20_NSUP); + return NULL; + } + ni = &dvni20[nni20s++]; /* Pick unused NI20 */ + memset((char *)ni, 0, sizeof(*ni)); /* Clear it out */ + + /* Initialize generic device part of NI20 struct */ + iodv_setnull(&ni->ni_dv); /* Initialize as null device */ + + ni->ni_dv.dv_dflags = 0; + ni->ni_dv.dv_pifnwd = ni20_pifnwd; + ni->ni_dv.dv_cono = ni20_cono; + ni->ni_dv.dv_coni = ni20_coni; + ni->ni_dv.dv_datao = ni20_datao; + ni->ni_dv.dv_datai = ni20_datai; + + ni->ni_dv.dv_bind = NULL; /* Not a controller!! */ + ni->ni_dv.dv_init = ni20_init; /* Set up own post-bind init */ + ni->ni_dv.dv_reset = ni20_reset; /* System reset (clear stuff) */ + ni->ni_dv.dv_powoff = ni20_powoff; /* Power-off cleanup */ + + if (!ni20_conf(f, s, ni)) /* Do configuration stuff */ + return NULL; + + return &ni->ni_dv; +} + + +static int +ni20_init(struct device *d, + FILE *of) +{ + register struct ni20 *ni = (struct ni20 *)d; + size_t junk; + + /* Transform device # into NI20 unit # */ + if (DEVRH20(0) <= ni->ni_dv.dv_num && ni->ni_dv.dv_num < DEVRH20(8)) { + ni->ni_no = (ni->ni_dv.dv_num - DEVRH20(0)) >> 2; + } else { + if (of) fprintf(of, "Trying to init NI20 with non-Massbus device #\n"); + return FALSE; + } + if (ni->ni_dv.dv_num != NI20_DEV) { + if (of) fprintf(of, "Initing NI20 with non-standard device #\n"); + } + dchn_init(&ni->ni_dc, ni->ni_no); /* Init data channel */ + +#if 0 + memcpy(ni->ni_ethadr, ni20_ethadr, 6); /* Initial ROM E/N addr */ +#endif + ni_ethtodw(&ni->ni_dwethadr, ni->ni_ethadr); /* Also in PDP-10 fmt */ + ni_ethtodw(&ni20_dwbcadr, ni20_bcadr); /* Get bcast into 10 fmt */ + + ni->ni_state = NI20_ST_UNINT; /* Ensure ni20_stop not invoked */ + + /* Set up periodic recheck timer (in case queue locked, etc) */ + if (!ni->ni_chktmr) + ni->ni_chktmr = clk_tmrget(ni20_runclk, (void *)ni, + CLK_USECS_PER_MSEC); + clk_tmrquiet(ni->ni_chktmr); /* Immediately make it quiescent */ + ni->ni_docheck = FALSE; + + /* Initialize gross echo packet checking if necessary */ + if (ni->ni_ecchk && !(ni->ni_dedic) && (ni->ni_ecblen > 0)) { + /* Echo check for shared interface, must use buffer, ugh! */ + ni->ni_ecb = (struct niecbe *)malloc(ni->ni_ecblen + * sizeof(struct niecbe)); + if (!(ni->ni_ecb)) { + if (of) fprintf(of, "NI20 couldn't alloc echo check buffer\n"); + return FALSE; + } + ni->ni_ecbfuse = ni->ni_ecbffree = 0; + if (ni->ni_ectmo) { /* Use slow half-sec clock? */ + ni->ni_ectmr = clk_tmrget(ni20_ecpclk, (void *)ni, + (CLK_USECS_PER_SEC/2)); + clk_tmrquiet(ni->ni_ectmr); /* Immediately make it quiescent */ + ni->ni_ectact = FALSE; + } + } + +#if KLH10_DEV_DPNI20 + { + register struct dpni20_s *dpc; + struct dvevent_s ev; + + ni->ni_dpstate = FALSE; + if (!dp_init(&ni->ni_dp, sizeof(struct dpni20_s), + DP_XT_MSIG, SIGUSR1, (size_t)1600, /* in */ + DP_XT_MSIG, SIGUSR1, (size_t)1600)) { /* out */ + if (of) fprintf(of, "NI20 subproc init failed!\n"); + return FALSE; + } + ni->ni_sbuf = dp_xsbuff(&(ni->ni_dp.dp_adr->dpc_todp), &junk); + ni->ni_rbuf = dp_xrbuff(&(ni->ni_dp.dp_adr->dpc_frdp), &junk); + + ni->ni_dv.dv_dpp = &(ni->ni_dp); /* Tell CPU where our DP struct is */ + + /* Set up NI20-specific part of shared DP memory */ + dpc = (struct dpni20_s *) ni->ni_dp.dp_adr; + dpc->dpni_dpc.dpc_debug = ni->ni_dpdbg; /* Init DP debug flag */ + if (cpu.mm_locked) /* Lock DP mem if CPU is */ + dpc->dpni_dpc.dpc_flags |= DPCF_MEMLOCK; + +#if DPNI20_LSAP + dpc->dpni_ver = DPNI20_VERSION; + dpc->dpni_attrs = 0; + if (ni->ni_lsapf) /* Pass on LSAP value if any */ + { + dpc->dpni_attrs |= DPNI20F_LSAP; + dpc->dpni_lsap = ni->ni_lsap; + } +#endif + dpc->dpni_backlog = ni->ni_backlog; /* Pass on backlog value */ + dpc->dpni_dedic = ni->ni_dedic; /* Pass on dedicated flag */ + dpc->dpni_decnet = ni->ni_decnet; /* Pass on DECNET flag */ + dpc->dpni_doarp = ni->ni_doarp; /* Pass on DOARP flag */ + dpc->dpni_rdtmo = ni->ni_rdtmo; /* Pass on RDTMO value */ + + if (ni->ni_ifnam) /* Pass on interface name if any */ + strncpy(dpc->dpni_ifnam, ni->ni_ifnam, sizeof(dpc->dpni_ifnam)-1); + else + dpc->dpni_ifnam[0] = '\0'; /* No specific interface */ + memcpy((char *)dpc->dpni_ip, /* Set our IP address for filter */ + ni->ni_ipadr, 4); + memcpy(dpc->dpni_eth, /* Set EN address if any given */ + ni->ni_ethadr, 6); /* (all zero if none) */ + + /* Register ourselves with main KLH10 loop for DP events */ + + ev.dvev_type = DVEV_DPSIG; /* Event = Device Proc signal */ + ev.dvev_arg.eva_int = SIGUSR1; + ev.dvev_arg2.eva_ip = &(ni->ni_dp.dp_adr->dpc_todp.dpx_donflg); + if (!(*ni->ni_dv.dv_evreg)((struct device *)ni, ni20_evhsdon, &ev)) { + if (of) fprintf(of, "NI20 event reg failed!\n"); + return FALSE; + } + + ev.dvev_type = DVEV_DPSIG; /* Event = Device Proc signal */ + ev.dvev_arg.eva_int = SIGUSR1; + ev.dvev_arg2.eva_ip = &(ni->ni_dp.dp_adr->dpc_frdp.dpx_wakflg); + if (!(*ni->ni_dv.dv_evreg)((struct device *)ni, ni20_evhrwak, &ev)) { + if (of) fprintf(of, "NI20 event reg failed!\n"); + return FALSE; + } + } +#endif /* KLH10_DEV_DPNI20 */ + + ni20_clear(ni); /* Clear NIA20 */ + + return TRUE; +} + +static void +ni20_reset(struct device *d) +{ + ni20_clear((struct ni20 *)d); +} + + +/* NI20_POWOFF - Handle "power-off" which usually means the KLH10 is +** being shut down. This is important if using a dev subproc! +*/ +static void +ni20_powoff(struct device *d) +{ + register struct ni20 *ni = (struct ni20 *)d; + + ni20_stop(ni); /* First stop NI cold */ +#if KLH10_DEV_DPNI20 + ni->ni_state = NI20_ST_UNINT; + (*ni->ni_dv.dv_evreg)( /* Flush all event handlers for device */ + (struct device *)ni, + NULL, /* No event handler proc */ + (struct dvevent_s *)NULL); + dp_term(&(ni->ni_dp), 0); /* Flush all subproc overhead */ + ni->ni_sbuf = NULL; /* Clear pointers no longer meaningful */ + ni->ni_rbuf = NULL; +#endif +} + +/* PI: Get PI function word +** +** Not clear which PI takes precedence; the PIA from the PCB or the +** CSR. They really should be identical. +** It appears that the PCB takes precedence, inasmuch as KLNI.MEM says +** the port cannot do a PI until the PCB PIA is set, and the T20 bootstrap +** code (and monitor startup) appears to screw up by treating the NI +** port like a RH20 initially -- if the NI responded to the CSR PI +** assignment, it would interrupt at RH20 level and generally confuse +** the software. What a mess. +** +** It appears that for T20, an unvectored interrupt is used. +** Again, not clear if this is because the IVA word of PCB is zero; +** it isn't even explicitly initialized to zero, it's just left alone! +** +** To avoid slowing up the KLH10 PI code, we'll compute the correct +** standard-dispatch vector here and feed it back in the RH so the +** PI RH20 handling stuff will be suitably faked out; if it is changed +** to pay attention to the PIFN_STD value, whatever is in the RH will +** be harmless anyway. +*/ + +static w10_t +ni20_pifnwd(struct device *d) +{ + register struct ni20 *ni = (struct ni20 *)d; + register w10_t w; + + LRHSET(w, PIFN_FSTD, /* Do a standard unvectored interrupt */ + ni->ni_pivec); /* But provide vector anyway! */ + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_pifnwd: %lo,,%lo]\r\n", + (long)LHGET(w), (long)RHGET(w)); + return w; +} + +/* CONO 18-bit conds out +** Args D, ERH +** Returns nothing +*/ +static insdef_cono(ni20_cono) +{ + register struct ni20 *ni = (struct ni20 *)d; + register uint18 cond = erh; + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_cono: %lo]\r\n", (long)erh); + + + /* B18 - Clear port - assume this stops it too */ + if (cond & NI20CO_CPT) { + ni20_clear(ni); + } + + /* Set most but not all RH bits to whatever CONO wants */ + ni->ni_cond &= ~(NI20CO_SEB|NI20CO_LAR|NI20CO_SSC + | NI20CO_CQA|NI20CO_DIS|NI20CO_ENA|NI20CO_MRN|NI20CO_PIA); + ni->ni_cond |= cond + & (NI20CO_SEB|NI20CO_LAR|NI20CO_SSC + | NI20CO_CQA|NI20CO_DIS|NI20CO_ENA|NI20CO_MRN|NI20CO_PIA); + + /* Bits to zap, if specified by CONO */ + if (cond & (NI20CI_EPE|NI20CI_FQE|NI20CI_DME|NI20CI_RQA)) { + ni->ni_cond &= ~(cond & + ( NI20CI_EPE /* B24 - Turn off Ebus Parity Error bit */ + | NI20CI_FQE /* B25 - Turn off Free Queue Error bit */ + | NI20CI_DME /* B26 - Turn off Data Mover Error bit */ + | NI20CI_RQA)); /* B28 - Turn off Response Queue Avail bit */ + } + + /* B32 - Run. Keep running if already on, else start running + ** using PC in RAR; 0 is normal start addr for KLNI port ucode. + */ + if (cond & NI20CO_MRN) { + if (!(ni->ni_state & NI20_STF_RUN)) + ni20_start(ni); /* Start the NI running */ + } else { + if (ni->ni_state & NI20_STF_RUN) + ni20_stop(ni); /* Stop the NI */ + } + + /* B30 - Disable. KL sets this with B32-Run to initialize port. + */ + if (cond & NI20CO_DIS) { + if (ni->ni_state == NI20_ST_RUNENA) + ni20_disable(ni); /* This turns on "disable complete" */ + else + ni->ni_lhcond |= NI20CI_DCP; /* Turn on "disable complete" */ + } else + ni->ni_lhcond &= ~NI20CI_DCP; /* Turn off "disable complete" */ + + /* B31 - Enable. KL sets this after initialized. + ** Must evidently stay on in all CONOs to keep port working. + */ + if (cond & NI20CO_ENA) { + /* Only has effect if running and disabled */ + if (ni->ni_state == NI20_ST_RUN) + ni20_enable(ni); /* Turns on "enable complete" */ + else + ni->ni_lhcond |= NI20CI_ECP; /* Turn on "enable complete" */ + } else + ni->ni_lhcond &= ~NI20CI_ECP; /* Turn off "enable complete" */ + + + /* B33-35 - PI channel assignment (0 = none) */ + ni->ni_pia = cond & NI20CO_PIA; + if (ni->ni_pia == ni->ni_ppia) { /* If consistent, enable PI */ + ni->ni_pilev = (1 << (7- ni->ni_pia)) & 0177; /* 0 if PIA=0 */ + } else + ni->ni_pilev = 0; /* Else disable PI */ + + if (ni->ni_dv.dv_pireq && (ni->ni_dv.dv_pireq != ni->ni_pilev)) { + /* Changed PIA while PI outstanding; flush it, let picheck re-req */ + ni20_piclr(ni); /* Clear it. */ + } + + if (cond & NI20CO_CQA) { /* KL saying cmds now available? */ + if (ni->ni_state & NI20_STF_RUN) { + ni->ni_cmdqf = TRUE; + ni20_run(ni); /* Check command queue for KL input! */ + } + } + + /* Check for any changes to PI status */ + ni20_picheck(ni); +} + +/* CONI 36-bit conds in +** Args D +** Returns condition word +*/ +static insdef_coni(ni20_coni) +{ + register w10_t w; + register struct ni20 *ni = (struct ni20 *)d; + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_coni: %lo,,%lo]\r\n", + (long)ni->ni_lhcond, (long)ni->ni_cond); + + LRHSET(w, ni->ni_lhcond, ni->ni_cond); + + /* KLNI.MEM p.73 claims that B26 (DME) is cleared by reading the + ** CSR. Dunno if this is necessary, but why not. + */ + ni->ni_cond &= ~NI20CI_DME; + + return w; +} + +/* DATAO word out +** Args D, W +** Returns nothing +*/ +static insdef_datao(ni20_datao) +{ + register struct ni20 *ni = (struct ni20 *)d; + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_datao: %lo,,%lo]\r\n", + (long)LHGET(w), (long)RHGET(w)); + + if (ni->ni_cond & NI20CO_SEB) { + ni->ni_ebuf = w; /* Write EBUF word, for whatever it's worth */ + return; + } + + if (LHGET(w) & NI20DO_LRA) { /* Load RAR? */ + ni->ni_rar = (LHGET(w) & (NI20DO_RAR | NI20DO_MSB)) >> 4; + } else { + /* Deposit micro-halfword into loc specified by RAR. */ + /* For now, nothing. */ + } +} + +/* DATAI word in +** Args D +** Returns data word +*/ +static insdef_datai(ni20_datai) +{ + register struct ni20 *ni = (struct ni20 *)d; + register w10_t w; + + if (ni->ni_cond & NI20CO_SEB) { /* Read EBUF word? */ + w = ni->ni_ebuf; + + } else if (ni->ni_cond & NI20CO_LAR) { /* Select LAR? */ + LRHSET(w, (0400000 | (ni->ni_lar << 5) | 07), + H10MASK); + } else { + + /* Read LH or RH of word addressed by RAR. + ** Perhaps eventually this will actually read back what DATAOs have + ** written, just for kicks. For now, merely test for the addresses + ** holding the ucode version numbers. + */ + switch (ni->ni_rar) { + case (NI20_UA_VER<<1)+1: + LRHSET(w, 0, ((NI20_VERMAJ)<<12) | ((NI20_VERMIN<<6))); + break; + case (NI20_UA_EDT<<1)+1: + LRHSET(w, 0, (NI20_EDITNO<<6)); + break; + default: + op10m_setz(w); + break; + } + } + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_datai: %lo,,%lo]\r\n", + (long)LHGET(w), (long)RHGET(w)); + return w; +} + +/* +NI20 BEHAVIOR NOTES: + + On powerup, NI20 isn't running, CRAM is garbage. CRAM is 4096 + words of 60 bits. + + T20 starts SYSTEM:KNILDR.EXE which does explicit I/O instrs to + load the CRAM. NI isn't started. + + NI20 is later started up by T20 by setting CO_DIS and CO_MRN while + RAR is 0. Apparently, when proc state is changed from stopped to + running, it starts at RAR, which amounts to PC. + + Starting at 0 causes it to do a channel transfer; monitor must have + set up a 3-word transfer where the 3 words are + + + + + The "disable-done" flag is set when all's ready. T20 waits for 5 sec. + + "Enable" transitions to actual processing while running. + "Enable-done" ACKs this. T20 waits for 5 sec. + +My guess as to the reason for specifying the PIA twice (once in the CONO +bits, again in the PCB/initial-3-words) is as follows: + CONO PIA applies to interrupting the processor whenever any + "interrupt-KL" bits are set in the CSR. + + The PCB PIA applies to any functions that the port wants to carry + out using non-standard API function words; specifically, INC/DEC + of queue locks. + +So it would be possible for the CONO bits to be turned off while still +allowing the port to function. + + + DISABLING/STOPPING: When T20 stops the NI it first disables the +port and waits for "disable complete" to show up (with a 5 sec timeout) +before actually halting it by turning off the CO_MRN bit. + Now, T20 exhibits a possible bug whereby it only has one queue +entry for each of the RDNSA, LDPTT, and LDMCAT commands, which it forgets +about if they are put on the command queue and never make it back to the +response queue. If the NI is stopped while they're on the command queue, +it can *never* be properly restarted because the T20 code will never find +those entries again and thus never re-initialize properly (symptom is +that first CO_CQA->cmdchk given on startup will find an empty cmd queue). + So, disabling the port should probably try hard to clear +up the command queue in any way possible -- simply drop the SND DGMS and +execute the rest. (Obviously if the response Q is locked, this can't be +done, but try anyway). + + +QUEUE LENGTH notes: + + It doesn't appear as if the queue entry length field is actually +used by the NIA20, or at least not the way the spec describes it. The +only place where it seems relevant is when the NIA20 builds a DGRECV +entry, and the value of that field is set by the T20 driver to the +length in bytes of the data portion of the first BSD! See next note. + +RECEIVE notes: + + The KLNI.MEM spec is extremely unclear on how receive datagrams +are put together. + + The protocol type queues have entries built with their RDPBA +field set pointing to a BSD. The size of this buffer seems to be +the same as that in the (misused) queue header QHLEN field. + The BDSBA and BDSLN fields are also set for the first BSD. It +appears that only one BSD is ever set up, and the NIA20 is expected to +dump its data into that one BSD. So, it looks like the NIA20 must: + (1) Find the appropriate protocol queue (does it really use + "unknown" if there is none?) + (2) Pluck an entry off that queue, examine its RD_PBA field + to find the BSD header address. + (3) Use the BSD's BD_SBA and BD_SLN fields to dump the data + portion of the ethernet packet. + (4) Fill out RD_DA1, RD_SA1, RD_SIZ, RD_PTY as per spec. + (5) Hand to driver (link into response queue). + +On receive the driver always expects to see BSD format and doesn't +check the flag byte. The fields read by the T20 driver are: + RDDA1, RDSA1 - passed on to portal user + RDSIZ - always has 4 subtracted (for CRC) + RDPTY - ptcl type as received off wire (T20 driver swaps + bytes before passing on to portal user) + CMERC - To see if error + +If portal defined to be using padding, driver checks 1st 2 bytes of data +buffer to pluck out "data length" field (bytes swapped) and skip the +portal-user BP over it. This code doesn't even bother to track down the +pointer to the BSD header, it simply references the BSD's SBA directly +because it knows where it's located in the DGRECV entry. + + A few other driver-defined RD fields are used which the NIA20 + doesn't know about. These identify a portal (RDPID), virtual + address of its BSD buffer (RDVBA) + +Packet length field (RD_SIZ) is always set to actual dgm length plus +4, even though buffer itself is never overfilled (presumably this generates +an error of type 31, "queue length violation"). + + +LOOPBACK NOTES: + + The KLNI is effectively never allowed to receive any packets that +it transmits!!! (These are called, by the way, "echo" packets.) + + This happens because the KLNI only has one set of CRC logic, +shared between receive and transmit. Any attempt to generate CRC for +an outgoing packet will cause a CRC error when that packet is received +by the KLNI. When the KLNI detects an incoming CRC it checks whether +the monitor wants to accept CRC-error packets or not; if yes, it is put +on the unknown-PTT queue. If not, it is discarded without notification. +Neither TOPS-10 nor TOPS-20 appear to ever use this capability, although +I'm not completely sure. + The workaround for this CRC lossage is for the monitor to +compute the CRC itself, append it to the data, and set the +NI20_CMF_CRC flag in the SEND DGM command which means "CRC included"; +then the KLNI doesn't do an outgoing CRC and can apply its logic to +the incoming CRC. + However, neither TOPS-10 nor TOPS-20 ever set this flag or +generate their own CRC. Thus, neither expect to ever see datagrams +that they send out themselves! + + Emulating this behavior with a dedicated interface is easy +enough -- just check the source address of all received packets and +throw away the ones that match our own. Doing this with a shared +interface is rather more difficult if the hardware or OS allows +internal loopback (normally a good thing). Note that there could +be an unknown number of packets buffered in both directions +between the NI emulation and the actual hardware. + +One possible method: + When enabled, + - Keep a small ring buffer of outgoing possible-echo packets. + Save header & a digest/checksum for matching up. + - When a packet is received, check its source. If us, then + scan ring buffer for a match. If found, flush. + - Note that the only totally foolproof matchup method is to keep + around the complete packet until it's no longer needed. + - To determine when "no longer needed", use periodic timer to + reap buffer (remember range to flush at each tick). + +An enterprising variation would be to send out some initial +self-addressed or broadcast packets and see if they were received or +not. If not, no echo checking is necessary and it can be turned off. + +MCAT notes: + + The T20 driver does its own check of multi-cast datagrams it +receives, as if it doesn't trust the port to do the filtering properly! +Bizarre. + +*/ + +/* NI20 internal routines (internal registers etc) */ + +/* NI20_CLEAR - Clear NI port +** If device subproc is running, stop and kill it via ni20_stop. +*/ +static void +ni20_clear(register struct ni20 *ni) +{ + if (ni->ni_dv.dv_pireq) /* If PI request outstanding, */ + ni20_piclr(ni); /* clear it. */ + + /* Stop and flush any in-progress xfer? */ + + /* Need to figure out just which bits get cleared. */ + if (ni->ni_state & NI20_STF_RUN) + ni20_stop(ni); /* Stop NI if running */ + ni->ni_state = NI20_ST_UNINT; /* Force known state - RESET */ + + ni->ni_lhcond = NI20CI_PPT /* Reset LH CONI bits - say NI Port present */ + | NI20CI_PID; /* Say port type ID = 7 */ + ni->ni_cond = 0; /* Reset all RH CONI bits */ + ni->ni_pia = 0; + ni->ni_rar = 0; /* Clear RAR */ + ni->ni_lar = 0; /* May as well do the rest... */ + op10m_setz(ni->ni_ebuf); + + /* ni_ethadr is at bind time to a default value, then we hope it's + ** later set by ni20_iniable(). + */ + ni->ni_staflgs = 0; /* Station flags (NI20_RSF_*) */ + ni->ni_retries = 5; /* # retries (default 5, T20 sets to 16) */ + + ni->ni_pcba = 0; /* Phys addr of PCB */ + ni->ni_pcbvp = NULL; /* Ptr to actual PCB memory */ + ni->ni_ppia = 0; /* Port PIA */ + op10m_setz(ni->ni_ivec); /* Port interrupt vector */ + ni->ni_upqelen = 0; /* Unknown Ptcl Queue Entry len, in words */ + ni->ni_pttvp = NULL; /* Ptr to PTT in 10 memory */ + ni->ni_mcatvp = NULL; /* Ptr to MCAT in 10 memory */ + ni->ni_rcbvp = NULL; /* Ptr to counters in 10 memory */ + + dchn_clear(&ni->ni_dc); /* Data Channel vars */ + + ni->ni_istate = 0; /* Internal state for timeouts etc */ + ni->ni_cmdqf = FALSE; /* TRUE if want to check cmd queue */ + ni->ni_qepa = 0; /* Active queue entry (needs relinking) */ + ni->ni_qhpa = 0; /* Active queue header (relink here) */ + ni->ni_pktinf = FALSE; /* TRUE if have packet input waiting */ + + ni->ni_nptts = 0; /* # of valid entries in PTT */ + ni->ni_nmcats = 0; /* # of valid entries in MCAT */ + + memset((char *)(ni->ni_cnts), 0, sizeof(ni->ni_cnts)); + + clk_tmrquiet(ni->ni_chktmr); /* Force timer to be quiescent */ + ni->ni_docheck = FALSE; +} + +/* NI20_PICHECK - Check NI20 conditions to see if PI should be attempted. +*/ +static void +ni20_picheck(register struct ni20 *ni) +{ + /* If any possible interrupt bits are set */ + if ((ni->ni_lhcond & (NI20CI_CPE | NI20CI_MBE)) /* LH err bits */ + || (ni->ni_cond & (NI20CI_EPE | NI20CI_DME /* RH err bits */ + | NI20CI_FQE | NI20CI_RQA))) { /* RH norm int bits */ + + ni20_pi(ni); + return; + } + + /* Here, shouldn't be requesting PI, so if our request bit is set, + ** turn it off. + */ + if (ni->ni_dv.dv_pireq) { /* If set while shouldn't be, */ + ni20_piclr(ni); /* Clear it! */ + } +} + + +/* NI20_PI - trigger PI for selected NI20. +** This could perhaps be an inline macro, but for now +** having it as a function helps debug. +*/ +#if 1 +static int savedebug = 0; +#endif + +static void +ni20_pi(register struct ni20 *ni) +{ + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_pi: %o]", ni->ni_pilev); + + if (ni->ni_pilev /* If have non-zero PIA */ + && !(ni->ni_dv.dv_pireq)) { /* and not already asking for PI */ + (*ni->ni_dv.dv_pifun)(&ni->ni_dv, ni->ni_pilev); /* then do it! */ +#if 1 + if (NIDEBUG(ni)) { + savedebug = cpu.mr_debug; + cpu.mr_debug = 1; /* Temp hack - get more info */ + } +#endif + + } +} + +/* NI20_PICLR - Clear PI for selected NI20. +** This would normally be inline code, but want it a fn for debugging. +*/ +static void +ni20_piclr(register struct ni20 *ni) +{ + if (NIDEBUG(ni)) { + fprintf(NIDBF(ni), "[ni20_piclr:%o]", ni->ni_dv.dv_pireq); +#if 1 + cpu.mr_debug = savedebug; /* Temp hack - restore flag */ +#endif + } + + (*ni->ni_dv.dv_pifun)(&ni->ni_dv, 0); +} + +static void +ni20_cperr(register struct ni20 *ni, + int err) /* 12-bit PC to indicate specific error */ +{ + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_cperr: %o]", err); + + ni->ni_lar = err; /* Set LAR to error-code PC */ + ni->ni_lhcond |= NI20CI_CPE; /* Say CRAM Parity Error */ + ni20_stop(ni); /* Stop NI */ + ni20_pi(ni); /* Invoke PI */ +} + +/* Misc auxiliaries for shuffling data */ + +/* Convert ethernet addr from 6-byte array to PDP-10 doubleword format +*/ +static void +ni_ethtodw(dw10_t *da, register unsigned char *ea) +{ + register dw10_t d; + + LRHSET(d.w[0], ((ea[0]&0377)<<10) | ((ea[1]&0377)<<2) | ((ea[2]>>6)&03), + ((ea[2]&077)<<12) | ((ea[3]&0377)<<4)); + LRHSET(d.w[1], ((ea[4]&0377)<<10) | ((ea[5]&0377)<<2), 0); + *da = d; +} + +/* Convert ethernet addr from PDP-10 doubleword format to 6-byte array +*/ +static void +ni_dwtoeth(register unsigned char *ea, register dw10_t d) +{ + ea[0] = LHGET(d.w[0])>>10; + ea[1] = (LHGET(d.w[0])>>2) & 0377; + ea[2] = ((LHGET(d.w[0])<<6) | (RHGET(d.w[0])>>12)) & 0377; + ea[3] = (RHGET(d.w[0])>>4) & 0377; + ea[4] = LHGET(d.w[1])>>10; + ea[5] = (LHGET(d.w[1])>>2) & 0377; +} + +/* Copy byte array into PDP-10 words +*/ +static void +ni_8stows(register vmptr_t vp, + register unsigned char *ucp, + register unsigned int bcnt) +{ + for (; bcnt >= 4; bcnt -= 4, ucp += 4, vp = vm_padd(vp,1)) { + vm_psetxwd(vp, + ((ucp[0]&0377)<<10) | ((ucp[1]&0377)<<2) | ((ucp[2]>>6)&03), + ((ucp[2]&077)<<12) | ((ucp[3]&0377)<<4)); + } + if (bcnt) { + switch (bcnt) { + case 3: + vm_psetxwd(vp, + ((ucp[0]&0377)<<10) | ((ucp[1]&0377)<<2) | ((ucp[2]>>6)&03), + ((ucp[2]&077)<<12)); + break; + case 2: + vm_psetxwd(vp, + ((ucp[0]&0377)<<10) | ((ucp[1]&0377)<<2), + 0); + break; + case 1: + vm_psetxwd(vp, + ((ucp[0]&0377)<<10), + 0); + break; + } + } +} + +/* Copy PDP-10 words into byte array +*/ +static void +ni_wsto8s(register unsigned char *ucp, + register vmptr_t vp, + register unsigned int bcnt) +{ + register w10_t w; + + for (; bcnt >= 4; bcnt -= 4, vp = vm_padd(vp,1)) { + w = vm_pget(vp); + *ucp++ = LHGET(w)>>10; + *ucp++ = (LHGET(w)>>2) & 0377; + *ucp++ = ((LHGET(w)<<6) | (RHGET(w)>>12)) & 0377; + *ucp++ = (RHGET(w)>>4) & 0377; + } + if (bcnt) { + w = vm_pget(vp); + switch (bcnt) { + case 3: + ucp[2] = ((LHGET(w)<<6) | (RHGET(w)>>12)) & 0377; + case 2: + ucp[1] = (LHGET(w)>>2) & 0377; + case 1: + ucp[0] = LHGET(w)>>10; + } + } +} + +/* NI20_START - Starts NI running +** If start address is 0, assumes running normal program. +** Otherwise, fails with an error. +*/ +static void +ni20_start(register struct ni20 *ni) +{ + register w10_t w; + register vmptr_t vp; + register int res; + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_start: RAR %lo",(long)ni->ni_rar); + + if (ni->ni_rar != 0) { + /* Handle abnormal start address by giving error */ + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), " (bad)]\r\n"); + ni20_cperr(ni, NI20_CPE_INTERR); /* Say "internal error" */ + return; + } + + /* Normal start; gobble initial data from channel setup. + ** Should be 3 words. + */ + ni->ni_dc.dc_sts = CSW_CLRSET; /* Clear channel status */ + ni->ni_dc.dc_wrt = 0; /* Want to read, not write */ + if (!dchn_ccwget(&ni->ni_dc)) { + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), ", ccwget failed]\r\n"); + ni20_cperr(ni, NI20_CPE_CHNERR); /* Say "channel error" */ + return; + } + if (ni->ni_dc.dc_wcnt < 3) { /* Want 3 words */ + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), ", ccnt=%d bad]\r\n", (int)ni->ni_dc.dc_wcnt); + ni20_cperr(ni, NI20_CPE_CHNERR); /* What else to do? */ + return; + } + + /* Gobble the 3 words: + ** 0: + ** 2: + */ + vp = vm_physmap(ni->ni_dc.dc_buf); + ni->ni_ppia = vm_pgetrh(vp+1) & 07; /* Get port's PIA from wd 1 */ + ni->ni_ivec = vm_pget(vp+2); /* Get port's intvec from wd 2 */ + if (op10m_skipn(ni->ni_ivec)) { + /* Warn if this is ever non-zero, but keep going. */ + fprintf(NIDBF(ni), "[ni20_start: PCB IVEC non-zero: %lo,,%lo]\r\n", + (long)LHGET(ni->ni_ivec), (long)RHGET(ni->ni_ivec)); + } + ni->ni_pivec = EPT_PI0 + (2 * ni->ni_ppia); /* See ni20_pifnwd */ + if (ni->ni_pia == ni->ni_ppia) { /* If consistent, enable PI */ + ni->ni_pilev = (1 << (7- ni->ni_pia)) & 0177; /* 0 if PIA=0 */ + } else + ni->ni_pilev = 0; /* Else disable PI */ + + w = vm_pget(vp); /* Get word 0 */ + ni->ni_pcba = ((((paddr_t)LHGET(w))<<18) | RHGET(w)) & MASK22; + ni->ni_pcbvp = vm_physmap(ni->ni_pcba); /* Get ptr to PCB */ + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), ", pcba=%lo pia=%o ivec=%lo,,%lo", + (long)ni->ni_pcba, ni->ni_pia, + (long)LHGET(ni->ni_ivec), (long)RHGET(ni->ni_ivec)); + + /* Now have pointer to PCB, gobble up its contents into internal vars + ** if necessary. Following the KLNI.MEM spec, 3 things are definitely + ** cached: Unknown ptcl QE length, PTT addr, MCAT addr. + */ + + /* KLNI.MEM appears to be wrong. TOPS-10 expects to be able to + ** set the PTT and possibly the MCAT and RCB pointers later. + ** So for now, these pointers are cleared, and set from PCB only + ** when the appropriate command is given. + ** + ** The KNI ucode appears to cache them when enabled. It also + ** reads the MCAT and PTT at that time, as well. + */ +#if 0 + ni->ni_upqelen = vm_pgetrh(vm_padd(ni->ni_pcbvp, NI20_PB_UQL)); + + w = vm_pget(vm_padd(ni->ni_pcbvp, NI20_PB_PTT)); + ni->ni_pttvp = vm_physmap(((((paddr_t)LHGET(w))<<18)|RHGET(w)) & MASK22); + w = vm_pget(vm_padd(ni->ni_pcbvp, NI20_PB_MTT)); + ni->ni_mcatvp = vm_physmap(((((paddr_t)LHGET(w))<<18)|RHGET(w)) & MASK22); + w = vm_pget(vm_padd(ni->ni_pcbvp, NI20_PB_RCB)); + ni->ni_rcbvp = vm_physmap(((((paddr_t)LHGET(w))<<18)|RHGET(w)) & MASK22); +#else + ni->ni_pttvp = NULL; + ni->ni_mcatvp = NULL; + ni->ni_rcbvp = NULL; +#endif + + + /* Start subproc here, or wait for enabling? */ +#if KLH10_DEV_DPNI20 + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), " - starting DP \"%s\"...", + ni->ni_dpname); + + /* HORRIBLE UGLY HACK: for AXP OSF/1 and perhaps other systems, + ** the virtual-runtime timer of setitimer() remains in effect even + ** for the child process of a fork()! To avoid this, we must + ** temporarily turn the timer off, then resume it after the fork + ** is safely out of the way. + ** + ** Otherise, the timer would go off and the unexpected signal would + ** chop down the DP subproc without any warning! + ** + ** Later this should be done in DPSUP.C itself, when I can figure a + ** good way to tell whether the code is part of the KLH10 or a DP + ** subproc. + */ + clk_suspend(); /* Clear internal clock if one */ + res = dp_start(&ni->ni_dp, ni->ni_dpname); + clk_resume(); /* Resume internal clock if one */ + + if (!res) { + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), " failed!]\r\n"); + else + fprintf(NIDBF(ni), "[ni20_start: Start of DP \"%s\" failed!]\r\n", + ni->ni_dpname); + ni20_cperr(ni, NI20_CPE_SLFTST); /* Startup self-test failed */ + return; + } + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), " started!]\r\n"); + + /* Hack! Because TOPS-10 and to a lesser extent TOPS-20 are unduly + ** sensitive to the length of time needed for the NI to come up (both + ** have a 5-second timeout), sometimes a total block of the CPU is + ** necessary in order to give the dpni20 subproc enough time to come up + ** up successfully. + */ + if (ni->ni_dpidly) + os_sleep(ni->ni_dpidly); /* Sleep for this many seconds */ +#else + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "]\r\n"); +#endif + + /* Set state to "running", assume disabled. + */ + ni->ni_state = NI20_ST_RUN; /* Running disabled */ +} + +/* NI20_STOP - Stops NI +*/ +static void +ni20_stop(register struct ni20 *ni) +{ + ni->ni_cond &= ~NI20CO_MRN; /* Ensure run bit off */ + if (!(ni->ni_state & NI20_STF_RUN)) /* If not running, */ + return; /* that's all. */ + + ni->ni_state = NI20_ST_HALT; /* Say stopped */ + + /* Stop stuff... */ +#if KLH10_DEV_DPNI20 + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_stop: stopping..."); + + if (ni->ni_dp.dp_chpid) { + dp_stop(&ni->ni_dp, 1); /* Say to kill and wait 1 sec for synch */ + } + + ni->ni_dpstate = FALSE; /* No longer there and ready */ + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), " stopped]\r\n"); +#endif +} + +/* NI20_ENABLE - Enables running NI to start handling packets +*/ +static void +ni20_enable(register struct ni20 *ni) +{ + register w10_t w; + + /* Reset delay for initial RDNSA command, if any. For T10. */ + ni->ni_c3dlyct = ni->ni_c3dly; + + /* Start subproc now if not already there */ +#if KLH10_DEV_DPNI20 + /* If subproc hasn't finished initializing yet, wait for it before + ** changing state. When DPNI_INIT is received, ni20_able() will be + ** invoked to carry out state change. + */ + if (!ni->ni_dpstate) { + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_enable: waiting for DP]"); + return; + } else +#endif + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_enable:]"); + + + /* It turns out that contrary to the KLNI.MEM spec, the actual + ** NI ucode caches this info when it's enabled, not when the + ** NI starts running. + ** Cached info is: + ** NI20_PB_UQL - length of unknown protocol queue entry (for what?) + ** NI20_PB_PTT - PTT address (and does initial LDPTT, with no response) + ** NI20_PB_MTT - MCAT address (and does initial LDMCAT, with no resp) + ** NI20_PB_LAD - addr of channel logout word 1 (for what? not used) + ** NI20_PB_RCB - RCB address + */ + ni->ni_upqelen = vm_pgetrh(vm_padd(ni->ni_pcbvp, NI20_PB_UQL)); + + w = vm_pget(vm_padd(ni->ni_pcbvp, NI20_PB_PTT)); + if (op10m_skipe(w)) + fprintf(NIDBF(ni), "[ni20_enable: WARNING: zero PTT ptr]"); + ni->ni_pttvp = vm_physmap(((((paddr_t)LHGET(w))<<18)|RHGET(w)) & MASK22); + ni20_ldptt(ni); /* Load PTT */ + + w = vm_pget(vm_padd(ni->ni_pcbvp, NI20_PB_MTT)); + if (op10m_skipe(w)) + fprintf(NIDBF(ni), "[ni20_enable: WARNING: zero MCAT ptr]"); + ni->ni_mcatvp = vm_physmap(((((paddr_t)LHGET(w))<<18)|RHGET(w)) & MASK22); + ni20_ldmcat(ni); /* Load MCAT, may generate DP cmd */ + + w = vm_pget(vm_padd(ni->ni_pcbvp, NI20_PB_RCB)); + if (op10m_skipe(w)) + fprintf(NIDBF(ni), "[ni20_enable: WARNING: zero RCB ptr]"); + ni->ni_rcbvp = vm_physmap(((((paddr_t)LHGET(w))<<18)|RHGET(w)) & MASK22); + + + /* All's cached, finished with enable */ + + ni->ni_state = NI20_ST_RUNENA; /* Say running enabled */ + ni->ni_lhcond |= NI20CI_ECP; /* Set "enable complete" */ +} + +/* NI20_DISABLE - Tells running NI to stop handling packets +*/ +static void +ni20_disable(register struct ni20 *ni) +{ + if (ni->ni_state != NI20_ST_RUNENA) + return; + +#if KLH10_DEV_DPNI20 + /* If subproc hasn't finished initializing yet, wait for it before + ** changing state. When DPNI_INIT is received, ni20_able() will be + ** invoked to carry out state change. + */ + if (!ni->ni_dpstate) { + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_disable: waiting for DP]"); + return; + } else +#endif + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_disable:Beg]"); + + /* Want to stop receiving dgms while still processing command + ** queue, so as to get it all flushed. + */ + ni->ni_state = NI20_ST_RUN; /* Say running disabled, to stop inp */ + + /* Copy check from CONO */ + if (ni->ni_cond & NI20CO_CQA) /* KL saying cmds now available? */ + ni->ni_cmdqf = TRUE; + + ni20_run(ni); /* Hope this all finishes */ + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_disable:End]\r\n"); + + ni->ni_lhcond |= NI20CI_DCP; /* Set "disable complete" */ +} + +#if KLH10_DEV_DPNI20 +/* NI20_INIABLE - Invoked when DPNI_INIT is received from dev subproc, +** indicating that it's ready for action. +** Here we check current CONI flags to see if 10 is asking for +** the NI20 to finish becoming enabled or disabled, and finish it. +*/ +static void +ni20_iniable(register struct ni20 *ni) +{ + register struct dpni20_s *dpni = (struct dpni20_s *) ni->ni_dp.dp_adr; + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_iniable!]"); + + /* DP now initialized, fetch any state we're waiting for. */ + ni->ni_dpstate = TRUE; /* DP is now initialized! */ + memcpy(ni->ni_ethadr, dpni->dpni_eth, 6); /* Get true ethernet addr! */ + ni_ethtodw(&ni->ni_dwethadr, ni->ni_ethadr); /* also in PDP-10 fmt */ + /* Maybe later copy back dpni_ifnam also? */ + + /* Now check CONI flags, see what to do */ + if (ni->ni_cond & NI20CO_MRN) { /* Wants us to be running */ + if ((ni->ni_cond & NI20CO_DIS) /* Wants to be disabled */ + && !(ni->ni_lhcond & NI20CI_DCP)) { /* and not complete yet */ + ni20_disable(ni); /* then disable it! */ + } else if ((ni->ni_cond & NI20CO_ENA) /* Wants to be enabled */ + && !(ni->ni_lhcond & NI20CI_ECP)) { /* and not complete yet */ + ni20_enable(ni); /* then enable it! */ + } else + return; + ni20_picheck(ni); /* If either change happened, check PI */ + } +} +#endif /* KLH10_DEV_DPNI20 */ + +/* NI20_CMDCHK - Checks for input on the command queue, and executes + whatever's there. Does as much as it can without blocking. + + Question - delink 1st command, or leave on queue until action +is commited? Latter allows continuing from aborts since next time +will just restart same command. + + Note that the real NI20 locks a queue only when taking an +entry off or putting it on. In between, while being processed, the +queues are unlocked, the entry is in limbo, and nothing (as far as I +know) points to it! + + Problem - what happens if the appropriate free queue is +already locked?? Rather than trying to preserve state and wait for +next wakeup, find out beforehand which queue to put packet on and then +punt if it's locked, before doing any other processing. + + Rule for determining where the entry is relinked is: +(1) If a response is requested, *OR* there was an error in processing the + command, it goes at the end of the Response Queue. +(2) Otherwise: + If command was SNDDG and the protocol type exists in the PTT, it + is linked onto the end of the appropriate free queue. + Otherwise (not SNDDG, or unknown type), it is linked onto the + end of the Unknown Protocol Type free queue. + +This is complicated enough (and, for the case of errors, unpredictable +enough) that perhaps it would be best after all to have a way of remembering +a "waiting-to-relink-entry" state. + +*/ + +/* Macro to build error status byte for returning from nicmd_* functions */ +#define ni_errbyte(sri,err) (((sri) ? (NI20_CMF_SRI>>10) : 0) \ + | (((err)&(NI20_CMF_ERR>>11))<<1) | (NI20_CMF_ERF>>10)) + +static int +ni20_cmdchk(register struct ni20 *ni) +{ + register paddr_t qh, qe; + register vmptr_t qep; + register w10_t w; + register int i; + +#if 0 + /* Check for possible delay, to help T10. Ugh. */ + if (ni->ni_inidlyct > 0) { /* Finished with initial delay? */ + /* Nope, still doing delay */ + + ni->ni_inidlyct--; + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), + "[ni20_cmdchk: dly=%ld]", (long) ni->ni_inidlyct); + /* Set up timer */ + if (!ni->ni_docheck) { /* Try again later */ + ni->ni_docheck = TRUE; /* Say timer active */ + clk_tmractiv(ni->ni_chktmr); /* Activate timer */ + } + return 0; /* Return incomplete */ + } +#endif + + qh = ni->ni_pcba + NI20_PB_CQI; + qe = ni_qeget(ni, qh); /* Get entry from command queue */ + + if (!qe) + return 0; /* Already locked, punt for now */ + if (qe == 1) { /* If nothing there, */ + ni->ni_cmdqf = FALSE; /* tell top-level no more */ + return 1; /* and pretend success.. */ + } + + /* Have entry, with lock still seized. */ +#if 0 + op10m_seto(w); + vm_pset(vm_physmap(qh + NI20_QH_IWD), w); /* Set -1 to unlock */ +#endif + + /* DANGER! At this point queue entry isn't linked anywhere! + ** Have to be sure that --nothing-- prevents us from either linking it back + ** in, or setting it up in ni_qep and ni_qhp for ditto. + */ + qep = vm_physmap(qe); + w = vm_pget(qep + NI20_CM_CMD); /* Get command opcode word */ + i = ((LHGET(w) & 03)<<6) | ((RHGET(w)>>12) & 077); /* Get cmd */ + + if (NIDEBUG(ni)) { + fprintf(NIDBF(ni), "[ni20_cmdchk: cmd=%o wd=%lo,,%lo qe=%lo]\r\n", + i, (long)LHGET(w), (long)RHGET(w), (long)qe); + } + + qh = ni->ni_pcba + NI20_PB_UQI; /* Unk-Ptcl is default relink queue */ + switch (i) { + case NI20_OP_SND: /* Send Datagram */ + i = nicmd_snddg(ni, qep); + /* Do special stuff to find correct relink queue */ + if (i == 0) { + qh = ni_pttfind(ni, + (int)vm_pgetrh(vm_padd(qep, NI20_CM_SNPTY)) & NI20_SNF_PTY); + if (!qh) + qh = ni->ni_pcba + NI20_PB_UQI; /* Unk-Ptcl is default */ + } + break; + case NI20_OP_LDM: /* Load Multicast Address Table */ + i = nicmd_ldmcat(ni, qep); + break; + case NI20_OP_LDP: /* Load Protocol Type Table */ +#if 1 + /* Check for possible delay, to help T10. Ugh. + ** ANF-10 has a 50-instruction window when starting up + ** (D8EONC between the calls to ETHSER and D8EOOF) during which + ** completion of the LDPTT sent by the ETHSER NU.OPN call will + ** cause bad things to happen -- D8EOOF is invoked before it's + ** supposed to. Bleah. + */ + while (ni->ni_c3dly) { /* Doing delay for LDPTT? */ + if (--(ni->ni_c3dlyct) < 0) { + /* If counted out, succeed and reset counter */ + ni->ni_c3dlyct = ni->ni_c3dly; + break; + } + /* Sigh, must delay. */ + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), + "[ni20_ldptt: dly=%ld]", (long) ni->ni_c3dlyct); + /* Ensure timer set up */ + if (!ni->ni_docheck) { /* Try again later */ + ni->ni_docheck = TRUE; /* Say timer active */ + clk_tmractiv(ni->ni_chktmr); /* Activate timer */ + } + /* Put command back at head of command queue!! */ + (void) ni_qeunget(ni, + (ni->ni_pcba + NI20_PB_CQI), qe); + return 0; /* Return incomplete */ + } +#endif + i = nicmd_ldptt(ni, qep); + break; + case NI20_OP_RCC: /* Read and Clear Counters */ + i = nicmd_rccnt(ni, qep); + break; +#if 0 + case NI20_OP_RCV: /* Datagram Received */ +#endif + case NI20_OP_WPL: /* Write PLI */ + i = nicmd_wrtpli(ni, qep); + break; + case NI20_OP_RPL: /* Read PLI */ + i = nicmd_rdpli(ni, qep); + break; + case NI20_OP_RSI: /* Read Station Information */ + i = nicmd_rdnsa(ni, qep); + break; + case NI20_OP_WSI: /* Write Station Information */ + i = nicmd_wrtnsa(ni, qep); + break; + default: + fprintf(NIDBF(ni), "[ni20_cmdchk: Illop=%o wd=%lo,,%lo qe=%lo]\r\n", + i, (long)LHGET(w), (long)RHGET(w), (long)qe); + i = ni_errbyte(0, NI20_ERR_URC); /* Unrecognized cmd */ + break; + } + + /* Command done, see where to link it back. + ** If error, or NI20_CMF_RSP flag set, always put on Response queue. + ** Otherwise, use default queue (normally Unknown-Ptcl-Type except + ** for SNDDG which may change it) + */ + op10m_tlz(w, NI20_CMF_STSB); /* Clear status byte */ + if (i) /* Returning error status? */ + op10m_tlo(w, ((h10_t)i) << 10); /* Set error */ + vm_pset(qep + NI20_CM_CMD, w); /* Store word back */ + + if (i || op10m_tlnn(w, NI20_CMF_RSP)) /* If err or Resp requested */ + qh = ni->ni_pcba + NI20_PB_RQI; /* Force use of Response queue */ + + return ni_qeput(ni, qh, qe); /* Link to end of right queue! */ +} + +/* NICMD_SNDDG - Send Datagram +** Note on protocol type: the 16-bit value in the SNDDG entry has +** its two bytes in low-high order, rather than network wire order +** (which has high byte first). Sigh. +*/ + + +#if !KLH10_DEV_NPNI20 + unsigned int ni20_slen; /* # bytes */ + unsigned char ni20_sbuf[NI20_MAXPKTLEN]; +#endif + +static int +nicmd_snddg(register struct ni20 *ni, register vmptr_t qep) +{ + register unsigned char *ucp; + int lhcmd; /* LH of cmd word (flags) */ + unsigned int tlen; /* Data length */ + unsigned int ptyp; /* Protocol type */ + dw10_t dest; + register w10_t w; + + /* Get entry's vitals into our locals */ + lhcmd = vm_pgetlh(vm_padd(qep, NI20_CM_CMD)); /* Get flags */ + tlen = vm_pgetrh(vm_padd(qep, NI20_CM_SNTXL)) & NI20_SNF_TXL; + w = vm_pget(vm_padd(qep, NI20_CM_SNPTY)); + ptyp = ((LHGET(w)&03)<<14) | (RHGET(w)>>4); /* PDP10 ptcl typ */ + dest = vm_pgetd(vm_padd(qep, NI20_CM_SNHAD)); /* Dest addr */ + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[nicmd_snddg: tl=%d pt=0x%x dst=%lo,%lo,%lo]\r\n", + tlen, ptyp, + (long)LHGET(dest.w[0]), (long)RHGET(dest.w[0]), + (long)LHGET(dest.w[1])); + + /* Do simple length checks here before attempting any copying. */ + if ((tlen < ETHER_MIN) && !(lhcmd & NI20_CMF_PAD)) { + /* Frame Too Short, and padding not requested, so fail. */ + return ni_errbyte(1, NI20_ERR_FTS); /* Note 1=err on xmit */ + } + if (tlen > (ETHER_MTU-2)) { /* Possible Too-Long error? */ + if ((tlen > ETHER_MTU) /* Check for non-pad fail */ + || (lhcmd & NI20_CMF_PAD)) { /* Check for pad fail */ + + /* Frame Too Long */ + return ni_errbyte(1, NI20_ERR_FTL); /* Note 1=err on xmit */ + } + } + +#if KLH10_DEV_DPNI20 + /* Set up raw packet buffer using DP comm area. + ** Should have already verified that sending is possible, but check; + ** if can't, pretend data overrun error (which may actually only apply + ** to input, oh well). + */ + if (!dp_xstest(&(ni->ni_dp.dp_adr->dpc_todp))) { + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[nicmd_snddg: DP overrun!]"); + return ni_errbyte(1, NI20_ERR_DOV); + } + ucp = ni->ni_sbuf; + +#else + /* Set up raw packet buffer. For now, simply clobber static area + ** for easier debugging. + ** Later, need to make sure we have one available before unlinking + ** a SNDDG queue entry! + */ + if (ni->ni_pktinf) { + /* For now, if input loopback packet already there, pretend + ** data overrun error. Sigh! + */ + return ni_errbyte(1, NI20_ERR_DOV); + } + ucp = ni20_sbuf; +#endif /* ! KLH10_DEV_DPNI20 */ + + /* Set up header */ + ni_dwtoeth(ucp + ETHER_PX_DST, dest); /* First goes dest addr */ + memcpy(ucp + ETHER_PX_SRC, ni->ni_ethadr, ETHER_ADRSIZ); /* then src */ + ucp[ETHER_PX_TYP] = (ptyp & 0377); /* ptyp in wrong order */ + ucp[ETHER_PX_TYP+1] = ((ptyp>>8) & 0377); /* So swap here */ + ucp += ETHER_PX_DAT; /* Finally point to data */ + + if (lhcmd & NI20_CMF_PAD) { /* If doing padding... */ + *ucp++ = (tlen & 0377); /* prepend 2 length bytes! Low 1st */ + *ucp++ = (tlen >> 8) & 0377; /* High 2nd */ + } + + if (lhcmd & NI20_CMF_BSD) { + /* BSD format, must track down and copy buffers */ + register uint32 totlen = 0; + + w = vm_pget(vm_padd(qep, NI20_CM_SNBBA)); /* BSD base addr */ + /* KLNI.MEM says ptr field is 24 bits, but we'll use 22 for now */ + op10m_tlz(w, 0777760); /* Leave low 22 bits */ + while (op10m_skipn(w)) { + register unsigned int blen; + register vmptr_t vp; + + vp = vm_physmap(w10topa(w)); /* Point to BSD */ + blen = vm_pgetrh(vm_padd(vp, NI20_BD_SLN)) & NI20_BDF_SLN; + if (blen) { + if ((totlen += blen) > tlen) /* Check for excessive len */ + break; + w = vm_pget(vm_padd(vp, NI20_BD_HDR)); /* Get wd with SBA */ + ni_wsto8s(ucp, vm_physmap(w10topa(w) & MASK22), blen); + ucp += blen; + } + w = vm_pget(vm_padd(vp, NI20_BD_NXA)); + /* KLNI.MEM says ptr field is 24 bits, but we'll use 22 for now */ + op10m_tlz(w, 0777760); /* Leave low 22 bits */ + } + + /* Done with buffer-copy loop, see if resulting total is right */ + if (totlen != tlen) { + /* Ugh, buffer length error! Fail; later versions of code + ** may need to free up the packet buffer, when it stops + ** being static. + */ + return ni_errbyte(1, NI20_ERR_BLV); /* Note 1=xmit */ + } + + } else { + /* Non-BSD format, data immediately follows in entry */ + ni_wsto8s(ucp, vm_padd(qep, NI20_CM_SNDTA), tlen); + ucp += tlen; + } + + /* One more check of pad flag */ + if (lhcmd & NI20_CMF_PAD) { + tlen += 2; /* Account for prepended len bytes */ + if (tlen < ETHER_MIN) { + memset(ucp, 0, ETHER_MIN - tlen); /* Add zero padding */ + tlen = ETHER_MIN; /* Out to minimum data length */ + } + } + + /* Packet buffer all set! + ** Do magic to get it transmitted. + */ + tlen += ETHER_HDRSIZ; /* Send entire packet */ + ni->ni_cnts[NI20_RC_BX] += tlen; /* Update # bytes and frames */ + ni->ni_cnts[NI20_RC_FX]++; + if (op10m_tlnn(dest.w[0], 02000)) { /* Check B7 - multicast bit */ + /* Gross kludge! TOPS-20 always subtracts the # of multicasts it + ** sends from the NI20_RC_RF counter, apparently because the NI port + ** always incurs a receive-failure increment every time a multicast + ** packet is transmitted. This is probably due to the CRC error + ** on self-addressed packets. + ** So, to keep users from gasping when they see negative + ** "receive failure" counts (using the NCP or IPHOST utilities), + ** this hack emulates the NI lossage. + */ + ni->ni_cnts[NI20_RC_RF]++; /* Increment "receive failure" cnt */ + } + + /* One last thing -- ugly check for possible echo packet! */ + if (ni->ni_ecchk && ni->ni_ecblen /* Doing echo-check buffering? */ + && (op10m_tlnn(dest.w[0], 02000) /* If multicast, or to */ + || ( op10m_came(ni->ni_dwethadr.w[0], dest.w[0]) /* self */ + && op10m_came(ni->ni_dwethadr.w[1], dest.w[1])))) { + ni_ecpstore(ni, /* Remember the packet! */ +#if KLH10_DEV_DPNI20 + ni->ni_sbuf, +#else + ni20_sbuf, +#endif + tlen); + } + +#if KLH10_DEV_DPNI20 + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[nicmd_snddg: DP send: %d]", tlen); + dp_xsend(&(ni->ni_dp.dp_adr->dpc_todp), DPNI_SPKT, (size_t)tlen); +#else + /* For now, set up something to pretend received it on loopback?? */ + + ni20_slen = tlen; + ni->ni_pktinf = TRUE; + + if (!ni->ni_docheck) { + ni->ni_docheck = TRUE; /* Say to poll for input */ + clk_tmractiv(ni->ni_chktmr); /* Activate timer */ + } +#endif /* ! KLH10_DEV_DPNI20 */ + + return 0; /* Success... */ + +} + +/* Other misc commands from 10 */ + +/* NICMD_LDMCAT - Multicast Address Table +*/ +static int +nicmd_ldmcat(register struct ni20 *ni, register vmptr_t qep) +{ + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[nicmd_ldmcat:]"); + ni20_ldmcat(ni); /* Load MCAT table */ + return 0; /* No errors */ +} + +static void +ni20_ldmcat(register struct ni20 *ni) +{ + register vmptr_t vp; + register int i, n; + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_ldmcat: "); + + /* Set up to scan MCAT table from 10's memory, using previously + ** cached MCAT pointer set at ENABLE time. See ni20_enable(). + */ + if (!(vp = ni->ni_mcatvp)) + panic("ni20_ldmcat: null MCAT ptr"); + n = 0; + for (i = 0; i < NI20_NMTT; ++i, vp = vm_padd(vp, NI20_MT_LEN)) { + register dw10_t d; + + d = vm_pgetd(vp); + if (op10m_trnn(d.w[1], NI20_MTF_ENA)) { + op10m_trz(d.w[0], 017); /* Clear internal MBZ parts */ + op10m_tlz(d.w[1], 03); + RHSET(d.w[1], 0); + ni->ni_mcat[n] = d; /* Store in internal table */ + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "(%d:%lo,%lo,%lo)", n, + (long)LHGET(d.w[0]), + (long)RHGET(d.w[0]), + (long)LHGET(d.w[1])); + ++n; /* Remember # we stored */ + } + } + ni->ni_nmcats = n; + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), " = %d of %d entries enabled]\r\n", n, i); + +#if KLH10_DEV_DPNI20 + /* Now attempt to tell the DP subproc about this new MCAT table, in case + ** it's OK to clobber the hardware. + ** Note that DP is always informed even if there are no entries, because + ** something might have been deleted. DP must figure out which by + ** remembering previous state of table. Sigh. + */ + { + register struct dpni20_s *dpni = (struct dpni20_s *) ni->ni_dp.dp_adr; + + if (n > DPNI_MCAT_SIZ) + n = DPNI_MCAT_SIZ; + for (i = 0; i < n; i++) { + ni_dwtoeth(dpni->dpni_mcat[i], ni->ni_mcat[i]); + } + dpni->dpni_nmcats = n; + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_ldmcat: DP cmd SETMCAT]\r\n"); + dp_xsend(&(ni->ni_dp.dp_adr->dpc_todp), DPNI_SETMCAT, (size_t)0); + + } +#endif +} + + + +/* NICMD_LDPTT - Protocol Type Table +** +** Note on protocol type: +** It appears this is kept in low-high byte order, which is the +** reverse of network order (which has high byte first). +** For consistency, keep the 16-bit value in its PDP10 format. +*/ +static int +nicmd_ldptt(register struct ni20 *ni, register vmptr_t qep) +{ + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[nicmd_ldptt:]"); + + ni20_ldptt(ni); /* Load PTT */ + return 0; /* No errors */ +} + +static void +ni20_ldptt(register struct ni20 *ni) +{ + register vmptr_t vp; + register int i, n; + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_ldptt: "); + + /* Set up to scan PTT table from 10's memory, using previously + ** cached PTT pointer set at ENABLE time. See ni20_enable(). + */ + if (!(vp = ni->ni_pttvp)) + panic("ni20_ldptt: null PTT ptr"); + + n = 0; + for (i = 0; i < NI20_NPTT; ++i, vp = vm_padd(vp, NI20_PT_LEN)) { + register dw10_t d; + + d = vm_pgetd(vp); + if (op10m_tlnn(d.w[0], NI20_PTF_ENA)) { + ni->ni_ptt[n].ptt_type = /* Set type in internal table */ + ((LHGET(d.w[0]) & 03)<<14) | (RHGET(d.w[0])>>4); + ni->ni_ptt[n].ptt_freeq = w10topa(d.w[1]); + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "(%d:0x%x,%lo)", + n, ni->ni_ptt[n].ptt_type, + (long)ni->ni_ptt[n].ptt_freeq); + + ++n; /* Remember # we stored */ + } + } + ni->ni_nptts = n; + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), " = %d of %d entries enabled]\r\n", n, i); +} + + +/* NICMD_RCCNT - Read and Clear Counters +*/ +static int +nicmd_rccnt(register struct ni20 *ni, register vmptr_t qep) +{ + register vmptr_t vp; + register w10_t w; + register int i; + h10_t cmdlh = vm_pgetlh(vm_padd(qep, NI20_CM_CMD)); + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[nicmd_rccnt:%s%s]\r\n", + (cmdlh & NI20_CMF_RSP) ? " Read" : "", + (cmdlh & NI20_CMF_CLR) ? " Clear" : "" ); + + /* KLNI.MEM claims counters are returned as part of response entry. + ** However, PCB has pointer to a buffer of counters and other + ** clues indicate that is where counts are returned. + ** For now, go with T20 and update buffer, not response entry. + */ + /* Also, note that the RCB pointer is cached at the same time as + ** the PTT and MCAT pointers; see ni20_enable(). + */ + if (!(vp = ni->ni_rcbvp)) + panic("nicmd_rccnt: null RCB ptr"); + + /* I wonder if the counts are only copied if CMF_RSP is given, + ** or always? Assuming always, and to RCB buffer... + */ +#if 0 + /* If wants a response, copy current counters */ + if (cmdlh & NI20_CMF_RSP) /* Copy counters into response */ + for (vp = vm_padd(qep, NI20_CM_CMD+1), +#else + for (vp = ni->ni_rcbvp, /* Copy counters into RCB */ +#endif + i = 0; i < NI20_RCLEN; ++i, vp = vm_padd(vp, 1)) { + LRHSET(w, (ni->ni_cnts[i] >> H10BITS)&H10MASK, + (ni->ni_cnts[i]&H10MASK)); + vm_pset(vp, w); + } + + + /* If then wants to clear them, do so... */ + if (cmdlh & NI20_CMF_CLR) { + memset((char *)(ni->ni_cnts), 0, sizeof(ni->ni_cnts)); + } + + return 0; /* No errors */ +} + +/* NICMD_WRTPLI - Write PLI +*/ +static int +nicmd_wrtpli(register struct ni20 *ni, register vmptr_t qep) +{ + fprintf(NIDBF(ni), "[nicmd_wrtpli: unimplem]\r\n"); + return ni_errbyte(0, NI20_ERR_INT); /* Internal error */ +} + +/* NICMD_RDPLI - Read PLI +*/ +static int +nicmd_rdpli(register struct ni20 *ni, register vmptr_t qep) +{ + fprintf(NIDBF(ni), "[nicmd_rdpli: unimplem]\r\n"); + return ni_errbyte(0, NI20_ERR_INT); /* Internal error */ +} + +/* NICMD_RDNSA - Read Station Information +** Note that the "version number" here is actually +** the "edit number". T20 ignores the RDNSA information, getting +** its version/edit info directly from a CRAM read; T10 however +** diligently checks the RDNSA result. +*/ +static int +nicmd_rdnsa(register struct ni20 *ni, register vmptr_t qep) +{ + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), + "[nicmd_rdnsa: en=%lo,%lo,%lo f=%o ucv=%d #m=%d #p=%d]\r\n", + (long)LHGET(ni->ni_dwethadr.w[0]), + (long)RHGET(ni->ni_dwethadr.w[0]), + (long)LHGET(ni->ni_dwethadr.w[1]), + ni->ni_staflgs, + NI20_EDITNO, NI20_NMTT, NI20_NPTT); + + if (vm_pgetlh(vm_padd(qep, NI20_CM_CMD)) & NI20_CMF_RSP) { + register vmptr_t vp = vm_padd(qep, NI20_CM_CMD+1); + register w10_t w; + + vm_psetd(vp, ni->ni_dwethadr); /* Return our ethernet addr */ + LRHSET(w, 0, ni->ni_staflgs); + vm_pset(vm_padd(vp, 2), w); + LRHSET(w, (NI20_EDITNO>>6)&03, + ((NI20_EDITNO<<12)|(NI20_NMTT<<6)|NI20_NPTT) & H10MASK); + vm_pset(vm_padd(vp, 3), w); + } + + return 0; /* No errors */ +} + +/* NICMD_WRTNSA - Write Station Information +*/ +static int +nicmd_wrtnsa(register struct ni20 *ni, register vmptr_t qep) +{ + dw10_t newadr; + int newflgs, newtries; + register vmptr_t vp = vm_padd(qep, NI20_CM_CMD+1); + + newadr = vm_pgetd(vp); /* Get desired ethernet addr */ + newflgs = vm_pgetrh(vm_padd(vp, 2)) & NI20_RSF_FLGMSK; + newtries = vm_pgetrh(vm_padd(vp, 3)) & NI20_WSF_RTY; + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), + "[nicmd_wrtnsa: en=%lo,%lo,%lo f=%o t=%d]\r\n", + (long)LHGET(newadr.w[0]), + (long)RHGET(newadr.w[0]), + (long)LHGET(newadr.w[1]), + newflgs, newtries); + + /* Now attempt to set things according to furnished parameters */ + + ni->ni_retries = newtries; /* Basically ignore this parameter */ + + if (newflgs & NI20_RSF_PMC) { + fprintf(NIDBF(ni), + "[ni20: Ignoring attempt to set promisc-multicast]\r\n"); + newflgs &= ~NI20_RSF_PMC; + } + if (newflgs & NI20_RSF_PRM) { + fprintf(NIDBF(ni), + "[ni20: Ignoring attempt to set promiscuous mode]\r\n"); + newflgs &= ~NI20_RSF_PRM; + } + ni->ni_staflgs = newflgs; + + /* Try to change ethernet address, but only if it's really changing. */ + if ( op10m_camn(ni->ni_dwethadr.w[0], newadr.w[0]) + || op10m_camn(ni->ni_dwethadr.w[1], newadr.w[1])) { +#if KLH10_DEV_DPNI20 + register struct dpni20_s *dpni = (struct dpni20_s *) ni->ni_dp.dp_adr; + + ni_dwtoeth(dpni->dpni_rqeth, newadr); /* Set up new-address arg */ + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[nicmd_wrtnsa: DP cmd SETETH]\r\n"); + dp_xsend(&(ni->ni_dp.dp_adr->dpc_todp), DPNI_SETETH, (size_t)0); +#else + fprintf(NIDBF(ni), + "[ni20: Ignoring attempt to set ethernet addr]\r\n"); +#endif + } + return 0; /* No errors */ +} + +/* Process received datagram. +** Three possible outcomes: +** 1 - Flush dgm, keep going (all's been handled) +** 0 - Keep dgm, block & wait (qeget is blocked) +** -1 - Flush dgm, block & wait (qeput is blocked) +** +** Note on protocol type: +** It appears that the PDP10 16-bit value is in low-high byte order +** rather than the network wire order of high byte first. +** So the received type needs its bytes swapped before it can be +** looked up. +*/ +#define DGRCV_WONFLS 1 +#define DGRCV_BLK 0 +#define DGRCV_BLKFLS -1 + +static int +nicmd_dgrcv(register struct ni20 *ni, + register unsigned char *ucp, /* Pointer to received datagram */ + unsigned int blen) /* # bytes in datagram */ +{ + register vmptr_t qep, qhp; + register w10_t w; + paddr_t qh, qe; + int lhcmd; /* LH of cmd word (flags) */ + int ukptf; /* Set TRUE if type unknown, not in PTT */ + unsigned int ptyp; /* Protocol type */ + dw10_t ethadr; + + if (blen < ETHER_HDRSIZ) { + /* Increment some error counter */ + ni->ni_cnts[NI20_RC_RF]++; /* Receive failure */ + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[nicmd_dgrcv: drop, len=%d < hdr]\r\n", blen); + /* Throw packet away */ + return DGRCV_WONFLS; /* Pretend success */ + } + + /* Now carry out received packet filtering per [KLNI.MEM 6.6] + ** Eventually this should largely be done by the host platform's OS + ** filtering. + ** Note that we check for echo packets, if necessary for the particular + ** hardware or config we're using, before doing anything else. See + ** explanation on comment page above titled "NI20 Behavior Notes". + */ + ni_ethtodw(ðadr, &ucp[ETHER_PX_DST]); /* Get dest */ + if (ni->ni_ecchk /* Must apply echo check? */ + && (memcmp(ni->ni_ethadr, /* Yep, see if source addr is us */ + &ucp[ETHER_PX_SRC], 6)==0) + && (ni->ni_dedic /* Yep, so if dedicated ifc */ + || ni_ecpcheck(ni, ucp, blen))) { /* or we remember sending */ + if (NIDEBUG(ni)) /* then flush it! */ + fprintf(NIDBF(ni), + "[nicmd_dgrcv: filtered echo pkt: %lo,%lo,%lo]\r\n", + (long)LHGET(ethadr.w[0]), (long)RHGET(ethadr.w[0]), + (long)LHGET(ethadr.w[1]) ); + /* Throw packet away */ + return DGRCV_WONFLS; /* Pretend success */ + } + + blen -= ETHER_HDRSIZ; /* Now OK to get # bytes actual ether data */ + + if (op10m_tlnn(ethadr.w[0], 02000)) { /* Check B7 - multicast bit */ + /* Multicast packet. + ** Update count here for simplicity - but note count will be wrong + ** if we have to block later for lack of a receive queue. + */ + ni->ni_cnts[NI20_RC_MCB] += blen; /* Update # bytes, frames */ + ni->ni_cnts[NI20_RC_MCF]++; + if (ni->ni_staflgs & (NI20_RSF_PRM|NI20_RSF_PMC)) + ; /* Won - promiscuous */ + else if (op10m_came(ni20_dwbcadr.w[0], ethadr.w[0]) /* Bcast? */ + && op10m_came(ni20_dwbcadr.w[1], ethadr.w[1])) + ; /* Won - all-ones broadcast */ + else { + /* Grovel through MCAT table filtering */ + register int i = ni->ni_nmcats; + while (--i >= 0) { + if ( op10m_came(ni->ni_mcat[i].w[0], ethadr.w[0]) + && op10m_came(ni->ni_mcat[i].w[1], ethadr.w[1])) { + break; + } + } + if (i < 0) { + /* Multicast packet failed */ + /* Bump some counter? Nothing looks promising. */ + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), + "[nicmd_dgrcv: MC filtered: %lo,%lo,%lo]\r\n", + (long)LHGET(ethadr.w[0]), (long)RHGET(ethadr.w[0]), + (long)LHGET(ethadr.w[1]) ); + /* Throw packet away */ + return DGRCV_WONFLS; /* Pretend success */ + } + ; /* Won - enabled in MCAT */ + } + } else { + /* Normal packet, see if addressed to us (or if we're promiscuous) */ + if ( op10m_came(ni->ni_dwethadr.w[0], ethadr.w[0]) + && op10m_came(ni->ni_dwethadr.w[1], ethadr.w[1])) + ; /* Won - addressed to us */ + else if (ni->ni_staflgs & NI20_RSF_PMC) + ; /* Won - promiscuous */ + else { + /* Non-multicast packet failed */ + /* Bump some counter? */ + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), + "[nicmd_dgrcv: filtered: %lo,%lo,%lo]\r\n", + (long)LHGET(ethadr.w[0]), (long)RHGET(ethadr.w[0]), + (long)LHGET(ethadr.w[1]) ); + /* Throw packet away */ + return DGRCV_WONFLS; /* Pretend success */ + } + } + + + /* First find protocol type to see if a queue entry exists. + ** In order to match up with the PTT, and for insertion into the DGRCV + ** entry, the 2 type bytes must be kept in low-high order, even though + ** this is the opposite of both the on-wire and PDP-10 order! The + ** monitor NI20 driver reswaps the bytes into high-low when it reads + ** the DGRCV entry... sigh. + */ + lhcmd = 0; + ukptf = FALSE; + ptyp = (ucp[ETHER_PX_TYP+1]<<8) | ucp[ETHER_PX_TYP]; + qh = ni_pttfind(ni, ptyp); + if (!qh) { + /* Should we turn it into error 12 (Unrec ptcl type, NI20_ERR_UPT) + ** if no match in PTT? + ** A: NO - in fact the NI20 ucode never generates + ** an error of this type! + */ + qh = ni->ni_pcba + NI20_PB_UQI; /* Unk-Ptcl is default */ + ukptf = TRUE; /* Remember it's unknown */ + } + qhp = vm_physmap(qh); + + /* Now see if can grab a queue entry */ + if (!(qe = ni_qeget(ni, qh))) { + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[nicmd_dgrcv: pt=0x%x Q locked]\r\n", ptyp); + + return DGRCV_BLK; /* Locked, so block and wait */ + } + + /* See if anything actually there */ + if (qe == 1) { + if (!ukptf) { /* If ptcl was enabled, */ + ni->ni_cond |= NI20CI_FQE; /* Trigger FreeQueueError */ + ni20_pi(ni); + + /* Here is where we should update the appropriate count for + ** this protocol type... it's enabled, but the free queue is empty. + */ + ni->ni_cnts[NI20_RC_D01 + ni_ipttfind(ni, ptyp)]++; + + } else { + /* Ptcl not enabled and no unknown-ptcl queue for it */ + ni->ni_cnts[NI20_RC_DUN]++; + } + /* Throw packet away */ + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[nicmd_dgrcv: drop, pt=0x%x%s freeq empty]\r\n", + ptyp, (lhcmd ? " (unk)" : "")); + return DGRCV_WONFLS; /* Pretend success */ + } +#if 0 + op10m_seto(w); + vm_pset(qhp + NI20_QH_IWD, w); /* Set -1 to unlock */ +#endif + /* DANGER! At this point queue entry isn't linked anywhere! + ** Have to be sure that --nothing-- prevents us from either linking it back + ** in, or setting it up in ni_qep and ni_qhp for ditto. + */ + + /* Got entry, fill it out! */ + qep = vm_physmap(qe); + LRHSET(w, 0, blen + ETHER_CRCSIZ); + vm_pset(vm_padd(qep, NI20_CM_RDSIZ), w); /* 16-bit RJ len */ + vm_psetd(vm_padd(qep, NI20_CM_RDDHA), ethadr); /* Already have dest */ + ni_ethtodw(ðadr, &ucp[ETHER_PX_SRC]); /* Get source */ + vm_psetd(vm_padd(qep, NI20_CM_RDSHA), ethadr); + LRHSET(w, (ptyp>>14) & 03, (ptyp&MASK14)<<4); /* pt still swapped */ + vm_pset(vm_padd(qep, NI20_CM_RDPTY), w); /* 16-bit type */ + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[nicmd_dgrcv: tl=%d pt=0x%x src=%lo,%lo,%lo]\r\n", + blen, ptyp, + (long)LHGET(ethadr.w[0]), (long)RHGET(ethadr.w[0]), + (long)LHGET(ethadr.w[1])); + + /* Now find BSD header address, which driver should have set up for + ** us. Gotta take it on faith here... + */ + w = vm_pget(vm_padd(qep, NI20_CM_RDPBA)); + if (op10m_skipe(w)) { + /* Boy are we gonna lose */ + fprintf(NIDBF(ni), "[nicmd_dgrcv: zero BSD!!!]\r\n"); + lhcmd = ((h10_t)ni_errbyte(0, NI20_ERR_QLV)) << 10; + /* Some kind of error response return here */ + + } else { + register vmptr_t bdp; + register unsigned int sln; + + bdp = vm_physmap(w10topa(w)); + sln = vm_pgetrh(vm_padd(bdp, NI20_BD_SLN)) & NI20_BDF_SLN; + if (sln < blen) { + /* Say queue length violation (not enough room in buffer) */ + lhcmd = ((h10_t)ni_errbyte(0, NI20_ERR_QLV)) << 10; + } else + sln = blen; + + /* Copy the data! */ + ni_8stows(vm_physmap(MASK22 & w10topa( + vm_pget(vm_padd(bdp, NI20_BD_HDR)))), + &ucp[ETHER_PX_DAT], sln); + } + + /* Queue entry (almost) all done! + ** Always link it onto response queue. + */ + LRHSET(w, lhcmd, ((NI20_OP_RCV&077)<<12)); /* May contain error */ + vm_pset(vm_padd(qep, NI20_CM_CMD), w); + + return ni_qeput(ni, ni->ni_pcba + NI20_PB_RQI, qe) + ? DGRCV_WONFLS : DGRCV_BLKFLS; /* Link in, return */ +} + +static paddr_t +ni_pttfind(register struct ni20 *ni, register unsigned int ptyp) +{ + register struct niptt *ptt; + register int i; + + /* KLNI.MEM claims PTT should be provided in order of increasing + ** protocol type, to speed up elimination of unknown types. However, + ** this appears not to be the case for T20 at least, so we need to + ** check all entries. + ** ALSO: The free queue pointer points to the FLINK word, not to + ** the interlock word! Hence the adjustment to the return value so + ** it's consistent with the other Queue Header pointers. + */ + + for (ptt = ni->ni_ptt, i = ni->ni_nptts; i > 0; --i, ++ptt) { + if (ptyp == ptt->ptt_type) /* If same type, */ + return ptt->ptt_freeq-1; /* won, return its queue! */ + } + return 0; +} + +/* Same as above routine but returns index instead, for benefit of +** code that wants to update counters. Hope this doesn't happen often. +** Returns -1 if not found; this becomes "unknown". +*/ +static int +ni_ipttfind(register struct ni20 *ni, register unsigned int ptyp) +{ + register struct niptt *ptt; + register int i; + + for (ptt = ni->ni_ptt, i = 0; i < ni->ni_nptts; ++i, ++ptt) { + if (ptyp == ptt->ptt_type) /* If same type, */ + return i; /* won, return its index */ + } + return -1; +} + +#if 0 /* Not used */ + +static void +ni_errclr(unsigned int qe) +{ + register vmptr_t qep = vm_physmap(qe + NI20_CM_CMD); + register w10_t w = vm_pget(qep); + + op10m_tlz(w, NI20_CMF_STSB); /* Clear status byte */ + vm_pset(qep, w); /* Store word back */ +} + +static void +ni_errset(unsigned int qe, int err) +{ + register vmptr_t qep = vm_physmap(qe); + register w10_t w = vm_pget(qep + NI20_CM_CMD); + + /* Set up response packet */ + op10m_tlz(w, NI20_CMF_STSB); /* Clear status byte */ + op10m_tlo(w, (((h10_t)err)<<10) | NI20_CMF_ERF); /* Set error */ + vm_pset(qep + NI20_CM_CMD, w); /* Store word back */ +} + +/* NI_ERRCMD - Generate error response for command. +** Stuffs error info into command word of entry, and links it +** into the Response queue. +*/ +static int +ni_errcmd(register struct ni20 *ni, unsigned int qe, int err) +{ + ni_errset(qe, err); /* Set up response packet */ + return ni_qeput(ni, ni->ni_pcba + NI20_PB_RQI, qe); + /* Link on ResponseQ */ +} +#endif /* 0 - unused */ + +/* NI_QEGET - Get queue entry from front of queue. +** Three possible outcomes: +** 0 - queue was locked, must block and wait. +** 1 - queue not locked, but was empty. +** QE - queue now locked, entry plucked off. +*/ +static paddr_t +ni_qeget(register struct ni20 *ni, + register unsigned int qh) +{ + register vmptr_t qhp, qep, qnp; + register w10_t w; + register paddr_t qe; + + qhp = vm_physmap(qh); + + /* First attempt to get lock - fail if can't */ + if (op10m_skipge(vm_pget(vm_padd(qhp, NI20_QH_IWD)))) { + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni_qeget: Q=%lo locked]", (long)qh); + return 0; /* Already locked, forget it */ + } + + /* Hurray, it's unlocked. Don't actually waste time locking it here, + ** since the fact we're running means the CPU isn't. + ** If the KLH10 goes multi-threaded this will have to change, of course. + */ +#if 0 + op10m_setz(w); + vm_pset(vm_padd(qhp, NI20_QH_IWD), w); /* Set 0 to lock */ +#endif + + /* Get phys addr of 1st queue entry */ + qe = w10topa(vm_pget(vm_padd(qhp, NI20_QH_FLI))); + + /* See if anything actually there */ + if (qe == (qh + NI20_QH_FLI)) { +#if 0 + op10m_seto(w); + vm_pset(qhp + NI20_QH_IWD, w); /* Set -1 to unlock */ +#endif + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni_qeget: Q=%lo empty]", (long)qh); + + return 1; /* and return bad value */ + } + + /* Get ptr to first queue entry and unlink from queue + ** NE = c(QE+FLI) Get ptr to next entry + ** NE backlink = QH+FLI Set it up to point back at QH + ** QH forwlink = NE Point QH to it + */ + + /* A little paranoia never hurt anyone. Make sure return value + ** conventions can't be confused with a real (bad) queue entry address! + */ + if (qe == 0 || qe == 1) { + fprintf(NIDBF(ni), "[ni_qeget: QH=%lo QE=%lo Bad?!]\r\n", + (long)qh, (long)qe); +#if 0 + op10m_seto(w); + vm_pset(qhp + NI20_QH_IWD, w); /* Set -1 to unlock */ +#endif + return 1; + } + qep = vm_physmap(qe); + w = vm_pget(vm_padd(qep, NI20_QE_FLI)); /* Get NE = wd addr of next */ + qnp = vm_physmap(w10topa(w)); /* Make it a ptr */ + vm_pset(vm_padd(qhp, NI20_QH_FLI), w); /* Fix up QH fwd link */ + patow10(w, qh + NI20_QH_FLI); /* QH+FLI */ + vm_pset(vm_padd(qnp, NI20_QE_BLI), w); /* Set NE backlink */ + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni_qeget: Q=%lo E=%lo #=%d]", (long)qh, (long)qe, + ni_qecnt(ni, qh)); + + return qe; +} + +#if 1 /* TOPS-10 CROCK */ + +/* NI_QEUNGET - Link entry to *head* of a queue. +** Dies if queue is locked by -10. +** If the KLH10 goes multi-threaded this will need to change. +*/ +static int +ni_qeunget(register struct ni20 *ni, + register unsigned int qh, /* Phys addr */ + register unsigned int qe) /* Phys addr */ +{ + register vmptr_t qhp = vm_physmap(qh); + register vmptr_t qep, qtp; + register w10_t w; /* Phys addr in word */ + + /* Verify that can lock it */ + if (op10m_skipge(vm_pget(qhp + NI20_QH_IWD))) { + fprintf(NIDBF(ni), "[ni_qeunget: internal error, Q=%lo E=%lo locked]", + (long)qh, (long)qe); + return 0; + } + + /* Hurray, it's unlocked. Don't actually waste time locking it here, + ** since the fact we're running means the CPU isn't. + ** If the KLH10 goes multi-threaded this will have to change, of course. + */ +#if 0 + op10m_setz(w); + vm_pset(qhp + NI20_QH_IWD, w); /* Set 0 to lock */ +#endif + + /* First set up queue entry, then link in. + ** + ** QE forwlink = QH+FLI + ** QE backlink = QH+FLI + ** TE = c(QH+BLI) Tail entry (is QH if Q empty) + ** HE = c(QH+FLI) Head entry (is QH if Q empty) + ** QE backlink = TE + ** QE forwlink = HE + ** TE forwlink = QE + ** HE backlink = QE + ** QH backlink = QE + ** QH forwlink = QE + */ + patow10(w, qh + NI20_QH_FLI); /* Get phys addr of QH as an entry */ + qep = vm_physmap(qe); + vm_pset(qep + NI20_QE_BLI, w); /* Set QE backlink -> QH */ + w = vm_pget(qhp + NI20_QH_FLI); /* Get HE = wd addr of head entry */ + vm_pset(qep + NI20_QE_FLI, w); /* Set QE forwlink -> head entry */ + qtp = vm_physmap(w10topa(w)); /* Get ptr to HE */ + patow10(w, qe + NI20_QE_FLI); /* Get QE = wd addr of new entry */ + vm_pset(qtp + NI20_QE_BLI, w); /* Set HE backlink -> QE */ + vm_pset(qhp + NI20_QH_FLI, w); /* Set QH forwlink -> QE */ + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni_qeunget: Q=%lo E=%lo #=%d]", + (long)qh, (long)qe, ni_qecnt(ni, qh)); + + + /* Now could unlock the queue, if it was locked to begin with. */ +#if 0 + op10m_seto(w); + vm_pset(qhp + NI20_QH_IWD, w); /* Set -1 to unlock */ +#endif + + return 1; /* Success! */ +} +#endif /* T10 crock */ + +/* NI_QEPUT - Link entry to end of a queue. +** If locked, remembers attempt by setting state vars +** ni_qhpa and ni_qepa, so next time NI20 is run, will re-try the link. +*/ +static int +ni_qeput(register struct ni20 *ni, + register unsigned int qh, /* Phys addr */ + register unsigned int qe) /* Phys addr */ +{ + register vmptr_t qhp = vm_physmap(qh); + register vmptr_t qep, qtp; + register w10_t w; /* Phys addr in word */ + + /* See if can lock it */ + if (op10m_skipge(vm_pget(qhp + NI20_QH_IWD))) { + /* Ugh, already locked, so remember for re-try. */ + ni->ni_qhpa = qh; + ni->ni_qepa = qe; + if (!ni->ni_docheck) { /* Poll again later */ + ni->ni_docheck = TRUE; /* Say to poll for input */ + clk_tmractiv(ni->ni_chktmr); /* Activate timer */ + } + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni_qeput: Q=%lo E=%lo locked]", + (long)qh, (long)qe); + return 0; + } + + /* Hurray, it's unlocked. Don't actually waste time locking it here, + ** since the fact we're running means the CPU isn't. + ** If the KLH10 goes multi-threaded this will have to change, of course. + */ +#if 0 + op10m_setz(w); + vm_pset(qhp + NI20_QH_IWD, w); /* Set 0 to lock */ +#endif + + /* First set up queue entry, then link in. + ** + ** QE forwlink = QH+FLI + ** TE = c(QH+BLI) Tail entry (is QH if Q empty) + ** QE backlink = TE + ** TE forwlink = QE + ** QH backlink = QE + */ + patow10(w, qh + NI20_QH_FLI); /* Get phys addr of QH as an entry */ + qep = vm_physmap(qe); + vm_pset(qep + NI20_QE_FLI, w); /* Set QE forwlink -> QH */ + w = vm_pget(qhp + NI20_QH_BLI); /* Get TE = wd addr of tail entry */ + vm_pset(qep + NI20_QE_BLI, w); /* Set QE backlink -> tail entry */ + qtp = vm_physmap(w10topa(w)); /* Get ptr to TE */ + patow10(w, qe + NI20_QE_FLI); /* Get QE = wd addr of new entry */ + vm_pset(qtp + NI20_QE_FLI, w); /* Set TE forwlink -> QE */ + vm_pset(qhp + NI20_QH_BLI, w); /* Set QH backlink -> QE */ + + /* Linked, now check to see whether to set CSR Resp-Queue-Avail bit */ + if ((qtp == vm_padd(qhp, NI20_QH_FLI)) /* If Q was empty before */ + && (qh == (ni->ni_pcba + NI20_PB_RQI))) { /* and Q is Response Q */ + + ni->ni_cond |= NI20CI_RQA; /* Say response Q available! */ + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni_qeput: Q=%lo E=%lo #=1 Set_RQA]", + (long)qh, (long)qe); + ni20_pi(ni); + + } else + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni_qeput: Q=%lo E=%lo #=%d]", + (long)qh, (long)qe, ni_qecnt(ni, qh)); + + + /* Now could unlock the queue, if it was locked to begin with. */ +#if 0 + op10m_seto(w); + vm_pset(qhp + NI20_QH_IWD, w); /* Set -1 to unlock */ +#endif + + return 1; /* Success! */ +} + +/* NI_QECNT - debugging subroutine to return # of entries on a queue. +** Subject to horrible lossage if 10's queue list is screwed up! +*/ +static int +ni_qecnt(register struct ni20 *ni, + register unsigned int qh) /* Phys addr of QH */ +{ + register paddr_t qe; + register w10_t w; /* Phys addr in word */ + register int cnt; + + qh += NI20_QH_FLI; /* Point to forward link in QH */ + qe = qh; + for (cnt = 0; ++cnt < 1000;) { + /* Check here to see if QE is valid phys addr?? */ + + w = vm_pget(vm_physmap(qe + NI20_QE_FLI)); /* Get link wd */ + qe = w10topa(w); /* Cvt to physaddr value */ + if (!qe) + return -1; /* Error return, zero pointer */ + if (qe == qh) + return cnt; /* Win return, points back to header */ + } + return -2; /* Error return, infinite loop */ +} + +#if KLH10_DEV_DPNI20 + +/* NI20_EVHSDON - Invoked by INSBRK event handling when +** signal detected from DP saying "done" in response to something +** we sent it. +** Basically this means the DP should be ready to accept another +** output packet. +*/ +static void +ni20_evhsdon(struct device *d, + register struct dvevent_s *evp) +{ + register struct ni20 *ni = (struct ni20 *)d; + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_evhsdon: %d]", + (int)dp_xstest(&(ni->ni_dp.dp_adr->dpc_todp))); + + /* Do possible DP command post-processing */ + switch (dp_xrcmd(&(ni->ni_dp.dp_adr->dpc_todp))) { + case DPNI_SETETH: /* Ethernet addr maybe changed! */ + { + register struct dpni20_s *dpni = + (struct dpni20_s *) ni->ni_dp.dp_adr; + memcpy(ni->ni_ethadr, dpni->dpni_eth, 6); /* Get true addr */ + ni_ethtodw(&ni->ni_dwethadr, ni->ni_ethadr); /* also in 10 fmt */ + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_evhsdon: New EN=%lo,%lo,%lo]\r\n", + (long)LHGET(ni->ni_dwethadr.w[0]), + (long)RHGET(ni->ni_dwethadr.w[0]), + (long)LHGET(ni->ni_dwethadr.w[1])); + } + break; + } + ni20_run(ni); /* Always do generic run check */ +} +#endif /* KLH10_DEV_DPNI20 */ + +#if KLH10_DEV_DPNI20 + +/* NI20_EVHRWAK - Invoked by INSBRK event handling when +** signal detected from DP saying "wake up"; the DP is sending +** us an input packet. +*/ +static void +ni20_evhrwak(struct device *d, + register struct dvevent_s *evp) +{ + register struct ni20 *ni = (struct ni20 *)d; + register struct dpx_s *dpx = &(ni->ni_dp.dp_adr->dpc_frdp); + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_evhrwak: %d]", (int)dp_xrtest(dpx)); + + if (dp_xrtest(dpx)) { /* Verify there's a message for us */ + switch (dp_xrcmd(dpx)) { + case DPNI_INIT: + ni20_iniable(ni); /* Do initial disable/enable */ + dp_xrdone(dpx); /* and ACK it */ + break; + + /* Kludge note: add an extra 4 bytes into "bytes received" counter + ** for each datagram received, since the real NI apparently includes + ** the 4 CRC bytes in its count, and TOPS-20 subtracts 4 per datagram + ** from the count in order to "correct" it before giving it to the + ** user (via NI%). Another example of emulating hardware bogosity. + */ + case DPNI_RPKT: + ni->ni_cnts[NI20_RC_BR] += (ni->ni_rcnt = dp_xrcnt(dpx)) + 4; + ni->ni_cnts[NI20_RC_FR]++; /* Update # bytes & frames */ + if (ni->ni_state == NI20_ST_RUNENA) { /* If running enabled */ + /* ni->ni_rbuf = ; Already set up */ + ni->ni_pktinf = TRUE; + ni20_run(ni); /* Go process it */ + } else { + default: + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_evhrwak: R flushed]"); + dp_xrdone(dpx); /* Else just ACK it */ + ni->ni_pktinf = FALSE; /* Ensure flushed */ + } + break; + } + } +} +#endif /* KLH10_DEV_DPNI20 */ + +/* NI20_RUNCLK - invoked by clock timeout code to "run" the NI20 +** if needed. +*/ +static int +ni20_runclk(void *arg) +{ + register struct ni20 *ni = (struct ni20 *)arg; + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_runclk:]"); + if (ni->ni_docheck) + ni20_run(ni); + return ni->ni_docheck ? CLKEVH_RET_REPEAT /* Repeat again later */ + : CLKEVH_RET_QUIET; /* No recheck */ +} + +/* NI20_RUN - Invoked whenever the NI20 needs to "run". +** Synchronously, invoked by clock timeout via ni20_rundef(). +** Asynchronously, invoked by insbreak event handling. +*/ +static void +ni20_run(register struct ni20 *ni) +{ + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_run:]"); + + for (;;) { + /* See if waiting to relink a queue entry */ + if (ni->ni_qhpa && ni->ni_qepa) { + if (!ni_qeput(ni, ni->ni_qhpa, ni->ni_qepa)) + break; /* Still couldn't do it */ + ni->ni_qhpa = ni->ni_qepa = 0; /* Done, clear state */ + } + + /* Always give priority to incoming packets. */ + while (ni->ni_pktinf) { + int res; +#if KLH10_DEV_DPNI20 + res = nicmd_dgrcv(ni, ni->ni_rbuf, ni->ni_rcnt); +#else + res = nicmd_dgrcv(ni, &ni20_sbuf[0], ni20_slen); +#endif + if (res == DGRCV_WONFLS || res == DGRCV_BLKFLS) { + ni->ni_pktinf = FALSE; +#if KLH10_DEV_DPNI20 + /* Tell DP that we're done with what it sent us */ + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_run: R done]"); + + dp_xrdone(&(ni->ni_dp.dp_adr->dpc_frdp)); +#endif + } + if (res != DGRCV_WONFLS) + break; /* Must block, so stop now */ + } + + /* Then check for outgoing or command packets */ + if (ni->ni_cmdqf) { +#if KLH10_DEV_DPNI20 + /* Before taking anything off cmd queue, ensure we can send + ** an ethernet datagram. Yes, this is kludgy; should only + ** block on actual sending, not on commands in general. + */ + if (!dp_xstest(&(ni->ni_dp.dp_adr->dpc_todp))) + break; +#endif + if (!ni20_cmdchk(ni)) + break; + } else { + ni->ni_docheck = FALSE; /* Nothing left to do */ + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_run: Done]"); + return; + } + + } + + /* Now what? Re-schedule self? */ + if (!ni->ni_docheck) { + ni->ni_docheck = TRUE; /* Say to check again later */ + clk_tmractiv(ni->ni_chktmr); /* Activate timer */ + } + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_run: resched]"); +} + +/* Echo Packet Check grossness. + See comment page with note on "Loopback" for explanation of all this. +*/ + +/* Remember datagram for later checking. +*/ +static void +ni_ecpstore(register struct ni20 *ni, + register unsigned char *ucp, /* Ptr to received dgm */ + unsigned int len) /* # bytes in datagram */ +{ + register struct niecbe *be; + + /* Always grab next "free" in ring. This will clobber oldest entry + ** if buffer is full. + */ + if (!(be = ni->ni_ecb)) + return; /* Sanity check */ + be += ni->ni_ecbffree; /* Point to first free */ + + /* Stuff data into entry */ + memcpy(be->niec_hdr, ucp, sizeof(be->niec_hdr)); /* Remember header */ + be->niec_digest = ni_ecpdigest(ucp + sizeof(be->niec_hdr), + len - sizeof(be->niec_hdr)); + be->niec_deathtmo = ni->ni_ectmo; + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni_ecpstore: added #%d]", ni->ni_ecbffree); + + /* Now bump index */ + if (++(ni->ni_ecbffree) >= ni->ni_ecblen) + ni->ni_ecbffree = 0; /* Wrap to start of ring buffer */ + if (ni->ni_ecbffree == ni->ni_ecbfuse) { + /* We've used up last buffer slot! Flush oldest one in order to + always maintain a gap of one. + */ + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni_ecpstore: buffer overflow, flushed #%d]", + ni->ni_ecbfuse); + if (++(ni->ni_ecbfuse) >= ni->ni_ecblen) + ni->ni_ecbfuse = 0; + } + + /* Tickle timer if necessary */ + if (!(ni->ni_ectact) && ni->ni_ectmr) { + clk_tmractiv(ni->ni_ectmr); /* Activate timer */ + ni->ni_ectact = TRUE; + } +} + +/* Check to see if datagram is a true echo packet; i.e. we remember + having sent it recently. +*/ +static int +ni_ecpcheck(register struct ni20 *ni, + register unsigned char *ucp, /* Ptr to received dgm */ + unsigned int len) /* # bytes in datagram */ +{ + register struct niecbe *be; + register int i; + register uint32 digest; + register int cnt = 0; /* For debugging */ + + if (!(be = ni->ni_ecb) /* Do nothing if no buffer */ + || (ni->ni_ecbfuse == ni->ni_ecbffree)) /* or nothing active */ + return FALSE; + digest = ni_ecpdigest(ucp + sizeof(be->niec_hdr), + len - sizeof(be->niec_hdr)); + for (be += (i = ni->ni_ecbfuse); i != ni->ni_ecbffree; ++cnt) { + if ((be->niec_digest == digest) + && (memcmp(be->niec_hdr, ucp, sizeof(be->niec_hdr))==0)) { + /* MATCHED!!! + Flush not only this entry but any others prior to it, on + assumption that packet ordering will be preserved. + */ + ni->ni_ecbfuse = /* Set new active start pos */ + ((++i < ni->ni_ecblen) ? i : 0); + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), + "[ni_ecpcheck: echo-flushed %ld up to #%d]", + (long) cnt, i); + return TRUE; + } + if (++i < ni->ni_ecblen) /* Bump to next entry */ + ++be; + else /* Wrap in ring buf */ + i = 0, be = ni->ni_ecb; + } + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni_ecpcheck: no match in %ld]", (long) cnt); + + return FALSE; /* Not an echoed packet */ +} + +/* Grind datagram data and return a digest/checksum value +*/ +static uint32 +ni_ecpdigest(register unsigned char *ucp, /* Ptr to received dgm */ + register int len) /* # bytes in datagram */ +{ + register uint32 digest = 0; + + while (--len >= 0) + digest = (digest<<1) + (digest>>31) + *ucp++; + + return digest; +} + +/* NI20_ECPCLK - invoked by clock timeout code to reap the echo-check buffer. +*/ +static int +ni20_ecpclk(void *arg) +{ + register struct ni20 *ni = (struct ni20 *)arg; + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_ecpclk:]"); + + if (ni->ni_ectact) { + register int i; + register int cnt, dcnt; /* For debugging */ + + /* Scan starting at first active entry and flush those that + ** have timed out. This is gross, but simpler than maintaining + ** delta values for now. + */ + cnt = dcnt = 0; + for (i = ni->ni_ecbfuse; i != ni->ni_ecbffree; ++cnt) { + if (--(ni->ni_ecb[i].niec_deathtmo) <= 0) { + if (++i >= ni->ni_ecblen) /* Bump to next entry */ + i = 0; + ni->ni_ecbfuse = i; /* Set new active start pos */ + ++dcnt; + continue; + } + if (++i >= ni->ni_ecblen) /* Bump to next entry */ + i = 0; + } + if (dcnt) { + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), + "[ni_ecptmo: flushed %d of %d, left #%d-#%d]", + dcnt, cnt, ni->ni_ecbfuse, ni->ni_ecbffree); + } + if (ni->ni_ecbfuse == ni->ni_ecbffree) { + ni->ni_ectact = FALSE; /* No more, silence timer */ + } + } + return ni->ni_ectact ? CLKEVH_RET_REPEAT /* Repeat again later */ + : CLKEVH_RET_QUIET; /* No recheck */ +} + +#if 0 +/* NI20_IOBEG - Called by drive to set up data channel just prior to +** starting an I/O xfer. +** Returns 0 if failure; drive should stop immediately and not +** invoke rhdv_iobuf, but must still call rhdv_ioend. +** Else returns # of block units (sectors or records) to do I/O for. +*/ +int +ni20_iobeg(struct device *drv, int wflg) +{ + register struct ni20 *ni = (struct ni20 *)(drv->dv_ctlr); + + ni->ni_dcsts = CSW_CLRSET; /* Clear channel status */ + ni->ni_dcwrt = wflg; /* Remember if writing */ + if (!nidc_ccwget(ni)) { + ni->ni_cond |= NI20CI_CE /* Say Channel Error */ + | NI20CI_CMD; /* and Command Done */ + ni20_pi(ni); /* Try to trigger PI */ + return 0; /* Tell drive we failed */ + } + + return ni->ni_bcnt; +} + + +/* NI20_IOBUF - Called by drive to query data channel to find source +** or dest buffer for I/O xfer. Can be called repeatedly for one xfer. +** Caller must provide # of words used from the last call (0 if none). +** In particular, final transfer MUST invoke this to tell the channel +** how many words were actually used. +** +** If returns 0, no data or space left. +** If returns +, OK, *avp NULL if skipping, else vmptr to mem. +** If returns -, error. +*/ +int +ni20_iobuf(struct device *drv, + register int wc, /* Max 11 bits of word count, so int OK */ + vmptr_t *avp) +{ + register struct ni20 *ni = (struct ni20 *)(drv->dv_ctlr); + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_iobuf: %d. => ", wc); + + if (!ni->ni_dcwcnt) { /* Nothing left */ + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "0]\r\n"); + return 0; + } + + if (wc) { + /* If drive is updating our IO xfer status, do it. */ + if ((ni->ni_dcwcnt -= wc) < 0) /* Update remaining wd cnt */ + panic("ni20_iobuf: drive overran chan!"); + + /* Update buffer pointer. Note check of reverse xfer */ + if (ni->ni_dcbuf) + ni->ni_dcbuf += (ni->ni_dcrev ? -wc : wc); + + /* See if word count ran out */ + if (ni->ni_dcwcnt == 0) { + /* Yep, was this the last cmd (halt or last xfer?) + ** If not, try to get another data xfer CCW. + */ + if (ni->ni_dchlt || !nidc_ccwget(ni)) { + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "0]\r\n"); + return 0; /* Done, or no more */ + } + } + } + + /* Here, we have a positive count, so return that. */ + wc = ni->ni_dcwcnt; + + /* Also return addr of buffer. But there are a few special cases + ** to check for, sigh... + */ + if (ni->ni_dcbuf) + *avp = vm_physmap(ni->ni_dcbuf); + else { /* Ugh! Special fill or skip hack. */ + if (ni->ni_dcwrt) { /* If writing, use fill wds from EPT */ + *avp = vm_physmap(cpu.mr_ebraddr + EPT_CB0); + if (wc > 4) + wc = 4; + } else { + *avp = NULL; /* Tell drive to skip input */ + } + } + if (ni->ni_dcrev) { /* If reverse xfer, do 1 wd at time, sigh */ + wc = 1; + } + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "%d. (%lo)]\r\n", wc, (long)(ni->ni_dcbuf)); + + return wc; +} + + + +/* NI20_IOEND - Called by drive when I/O finished, terminate data channel +** I/O xfer. Assumes that ni20_iobuf has been called to update +** status after each transfer or sub-transfer, so channel word count +** is accurate reflection of data xfer. +** State may be one of: +** Error - either in drive, or from data channel. Assumption is +** that error has set all appropriate error bits (specifically +** Channel Error if channel status must be stored), and +** triggered PI as well. +** +** Drive stopped early due to no more DC buffer space. Indicated by +** non-zero returned block count. +** Drive stopped normally, block count 0. +** +** Checks to see whether to set Short/Long WC Error. +** Stores channel status if requested by PTCR bit NI20DO_SES. +** +** May initiate next NI20 data transfer. +*/ +void +ni20_ioend(register struct device *drv, + int bc) /* Drive's final block count */ +{ + register struct ni20 *ni = (struct ni20 *)(drv->dv_ctlr); + + if (NIDEBUG(ni)) + fprintf(NIDBF(ni), "[ni20_ioend: %d.]\r\n", bc); + + + /* Update final IO xfer status */ + if (bc || ni->ni_dcwcnt) { + /* Drive still has stuff it wanted to do. + ** Set channel Short WC if WC==0, or Long WC if WC != 0, + ** and NI20 Chan Err. + */ + ni->ni_dcsts |= (ni->ni_dcwcnt ? CSW_LWCE : CSW_SWCE); + ni->ni_cond |= NI20CI_CE; + } + + /* See whether to store channel status */ + if ((NI20REG(ni, NI20_PTCR) & NI20DO_SES) /* If wanted state stored */ + || (ni->ni_cond & NI20CI_CE)) { /* or any kind of chan error */ + register vmptr_t vp; + register w10_t w; + + vp = vm_physmap(cpu.mr_ebraddr + ni->ni_eptoff); + + /* Store status bits and current cmd list ptr */ + if (ni->ni_dcwcnt) + ni->ni_dcsts |= CSW_NOWC0; + LRHSET(w, ni->ni_dcsts | ((ni->ni_clp >> H10BITS)&CSW_HIADR), + ni->ni_clp & H10MASK); + vm_pset(vp+NI20_EPT_CST(0), w); + + /* Reconstruct current CCW and store it */ + LRHSET(w, (LHGET(ni->ni_dcccw)&CCW_OP) + | (ni->ni_dcwcnt<<4) | ((ni->ni_dcbuf>>H10BITS)&CCW_ADR), + ni->ni_dcbuf & H10MASK); + vm_pset(vp+NI20_EPT_CCW(0), w); + } + + /* Now say command done, whatever happened. */ + ni->ni_cond &= ~NI20CI_PCRF; /* Primary regs now free */ + ni->ni_cond |= NI20CI_CMD; + ni20_pi(ni); /* Attempt triggering PI */ + + /* Now if no errors, check for secondary TCR and initiate it? */ +} +#endif /* 0 */ + +/* Massbus Data Channel routines. +** +*/ + +/* DCHN_INIT - Called when setting up internal data structures +*/ +void +dchn_init(register struct dvdchn *dc, + int mbcn) /* Massbus controller # (0-7) */ +{ + dc->dc_eptoff = mbcn * 4; /* Offset of EPT area */ + dchn_clear(dc); +} + +/* DCHN_CLEAR - Called to clear data channel while KL running +*/ +void +dchn_clear(register struct dvdchn *dc) +{ + /* Reset channel PC to point at 1st wd of channel area in EPT */ + dc->dc_clp = dc->dc_eptoff + cpu.mr_ebraddr; + + dc->dc_sts = CSW_CLRSET; /* Clear status */ + LRHSET(dc->dc_ccw, 0, 0); + dc->dc_wcnt = 0; + dc->dc_buf = 0; + dc->dc_hlt = TRUE; +} + +/* DCHN_CCWGET - called by device when drive wants to start xfer or needs more +** data/buffer space from channel. +*/ +int +dchn_ccwget(register struct dvdchn *dc) +{ + register w10_t w; + register paddr_t pa; + register int wc; + + /* Grovel down cmd list until hit valid xfer cmd, use that to set up */ + for (;;) { + /* Fetch current cmd word */ + w = vm_pget(vm_physmap(dc->dc_clp)); /* Get cmd word */ + pa = w10topa(w); + wc = (LHGET(w) & CCW_CNT) >> (22-18); + + switch (LHGET(w) & CCW_OP) { + case CCW_HLT: + /* Use addr as CLP for next call, and do stuff to halt. + ** Should this cause a "Short Word Count" channel error, or + ** something else since transfer was never started? + ** For now, pretend zero word count, fail as if short WC. + */ + dc->dc_clp = pa; /* Set new "PC" */ + dc->dc_buf = pa; /* Also set here in case status stored */ + dc->dc_ccw = w; /* Remember current cmd */ + dc->dc_wcnt = 0; + dc->dc_hlt = TRUE; + dc->dc_rev = 0; + return 0; + + case CCW_JMP: + dc->dc_clp = pa; /* Set up CLP and loop to effect jump */ + continue; + + case CCW_XFR: /* Forward transfer, no halt */ + dc->dc_hlt = 0; + dc->dc_rev = 0; + break; + + case CCW_XFR|CCW_XHLT: /* Forward transfer, halt */ + dc->dc_hlt = TRUE; + dc->dc_rev = 0; + break; + + case CCW_XFR|CCW_XREV|CCW_XHLT: + dc->dc_hlt = 0; + dc->dc_rev = TRUE; + break; + + case CCW_XFR|CCW_XREV: + dc->dc_hlt = TRUE; + dc->dc_rev = TRUE; + break; + + default: + /* Perhaps assert Channel Error (RH20CI_CE) here? */ + panic("dchn_ccwget: Bad CCW op %lo", (long)(LHGET(w) & CCW_OP)); + return 0; + } + + dc->dc_ccw = w; /* Remember current cmd */ + dc->dc_wcnt = wc; /* Set up word count */ + dc->dc_buf = pa; /* Set up address */ + dc->dc_clp = (dc->dc_clp + 1) & MASK22; /* Bump "PC" */ + return 1; + } +} + +#endif /* KLH10_DEV_NI20 */ diff --git a/src/dvni20.h b/src/dvni20.h new file mode 100644 index 0000000..f5229dd --- /dev/null +++ b/src/dvni20.h @@ -0,0 +1,469 @@ +/* DVNI20.H - NIA-20 Network Interface defniitions +*/ +/* $Id: dvni20.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1994, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dvni20.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#ifndef NI20_INCLUDED +#define NI20_INCLUDED 1 + +#ifdef RCSID + RCSID(dvni20_h,"$Id: dvni20.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Initialization & sole entry point for NI20 driver +*/ +#include "kn10dev.h" +extern struct device *dvni20_create(FILE *f, char *s); + +#ifndef NI20_NSUP +# define NI20_NSUP 1 +#endif + +#ifndef RH20_INCLUDED +# include "dvrh20.h" /* Need generic RH20 defs for data channel */ +#endif + +/* The NI port is a special case of the IPA20-L Computer Interconnect (CI) +** port hardware, which itself is a special-case Massbus/RH20 device. +** By convention the NI is Massbus/RH20 port #5. +*/ + +/* NI20 Device Number assignment +*/ + +#define NI20_DEV DEVRH20(5) + + +/* NI20 CONI bits (LH) +** Read-only by 10; set by port device. +*/ +#define NI20CI_PPT 0400000 /* B0 - Port present */ +#define NI20CI_DCC 0100000 /* B2 - Diag CSR change */ + /* (Set when 10 writes CSR; cleared when + ** port reads it). + */ +#define NI20CI_CPE 04000 /* B6 - CRAM parity error */ +#define NI20CI_MBE 02000 /* B7 - Mbus error */ +#define NI20CI_IDL 0100 /* B11 - Idle */ +#define NI20CI_DCP 040 /* B12 - Disable complete */ +#define NI20CI_ECP 020 /* B13 - Enable complete */ +#define NI20CI_PID 07 /* B15-17 - Port type ID (always 7) */ + +/* NI20 CONI/CONO bits (RH) +** All may be written by a CONO. +*/ +#define NI20CO_CPT 0400000 /* B18 - Clear port */ +#define NI20CO_SEB 0200000 /* B19 - Diag Select Ebuf */ + /* B20 - Diag Gen Ebus PE */ +#define NI20CO_LAR 040000 /* B21 - Diag Select LAR/SQR */ +#define NI20CO_SSC 020000 /* B22 - Diag Single Cyc */ + /* B23 - */ +#define NI20CI_EPE 04000 /* B24 - EBUS PARITY ERROR */ +#define NI20CI_FQE 02000 /* B25 - FREE QUEUE ERROR */ + /* Set by port; CONO of bit clears it! */ +#define NI20CI_DME 01000 /* B26 - DATA MOVER ERROR */ +#define NI20CO_CQA 0400 /* B27 - COMMAND QUEUE AVAILABLE */ +#define NI20CI_RQA 0200 /* B28 - RESPONSE QUEUE AVAILABLE */ + /* Set by port; CONO of bit clears it! */ + /* B29 - */ +#define NI20CO_DIS 040 /* B30 - DISABLE */ +#define NI20CO_ENA 020 /* B31 - ENABLE */ +#define NI20CO_MRN 010 /* B32 - MICRO-PROCESSOR RUN */ +#define NI20CO_PIA 07 /* B33-35 - PI channel assignment (0 = none) */ + + +/* NI20 DATAO/DATAI word format +** What values are read or written depends on the CONI bit states. +** The sign bit of a DATAO means "Load RAM address register" +*/ + +#define NI20DO_LRA 0400000 /* LH: B0 Load RAM address reg */ +#define NI20DO_RAR 0377740 /* LH: B1-12 RAR (if B0 set) */ +#define NI20DO_MSB 020 /* LH: B13 Select LH of u-word (MSBits) */ + +/* DATAI with +** SEB=0, LAR=1 Reads Last Address Register (addr of last u-instr +** fetched by port) +** <0> <1:13> <14> <15:35> +** LAR: 1 LAR 0 -1 +** +** SEB=1 Reads EBUF word +** +** SEB=0, LAR=0 Reads LH or RH of u-word addressed by RAR. +** <0:5> <6:35> +** -1 +*/ +/* DATAO with +** SEB=0 Writes RAR, or LH/RH of u-word addressed by RAR. +** If B0=0: <0:5> <6:35> +** N/A +** If B0=1: <0> <1:12> <13> <14:35> +** 1 RAR LH=1 N/A +** +** SEB=1 ?? Writes EBUF word? +** +*/ + + +#define NI20_UADR 0377740 /* 7777B12 - Ucode address field in RAR/LAR */ + +#define NI20_UA_VER 0136 /* Ucode address of major & minor ver #s */ +#define NI20_UA_EDT 0137 /* Ucode address of edit # */ +#define NI20_UVF_MAJ 0170000 /* RH: 17B23 Major version # in UA_VER LH */ +#define NI20_UVF_MIN 01700 /* RH: 17B29 Minor version # in UA_VER LH */ +#define NI20_UF_EDT 0177700 /* RH: 1777B29 Edit # in UA_EDT LH */ + +#define NI20_VERMAJ 01 /* Duplicate actual NIA20 ucode version */ +#define NI20_VERMIN 0 +#define NI20_EDITNO 0172 /* Edit # (171 is minimum acceptable to T20) */ + /* (167 is minimum acceptable to T10) */ + + /* CRAM Parity Error PCs, to indicate error type */ +#define NI20_CPE_INTERR 07750 /* Internal Error */ +#define NI20_CPE_SLFTST 07751 /* Startup self-test failed */ +#define NI20_CPE_CHNERR 07762 /* Channel Error */ + + /* Size of internal tables */ +#define NI20_NPTT 017 /* Max # of entries in Protocol Type Table */ +#define NI20_NMTT 017 /* Max # of entries in Multicast Address Table */ + +/* NOTE: the NI port spec claims that 16 entries are used for both +** the PTT and MCAT. However, both T10 and T20 allocate only 15 entries, +** and this is the size that the actual NI ucode returns for the RDNSA +** command. +** +** !!!HOWEVER!!! The actual NI ucode *really does* have 16 entries, +** and it *really does* read in 16 words for each table! +** +** Groan. +** Who knows whether this caused any actual lossage for real PDP10s. +** +** The KLH10 will do the Right Thing and only read 15 entries, as advertised. +*/ + +/* Port Control Block (PCB) definitions +*/ + +/* Offsets into PCB */ +enum { + NI20_PB_CQI=0, /* 0 COMMAND QUEUE INTERLOCK */ + NI20_PB_CQF, /* 1 COMMAND QUEUE FLINK */ + NI20_PB_CQB, /* 2 COMMAND QUEUE BLINK */ + NI20_PB_RS0, /* 3 RESERVED FOR SOFTWARE */ + NI20_PB_RQI, /* 4 RESPONSE QUEUE INTERLOCK */ + NI20_PB_RQF, /* 5 RESPONSE QUEUE FLINK */ + NI20_PB_RQB, /* 6 RESPONSE QUEUE BLINK */ + NI20_PB_RS1, /* 7 RESERVED */ + NI20_PB_UQI, /* 010 UNKNOWN PROTOCOL TYPE QUEUE INTERLOCK */ + NI20_PB_UQF, /* 011 UNKNOWN PROTOCOL TYPE QUEUE FLINK */ + NI20_PB_UQB, /* 012 UNKNOWN PROTOCOL TYPE QUEUE BLINK */ + NI20_PB_UQL, /* 013 UNKNOWN PROTOCOL TYPE QUEUE LENGTH */ + NI20_PB_RS2, /* 014 RESERVED */ + NI20_PB_PTT, /* 015 PROTOCOL TYPE TABLE STARTING ADDRESS */ + NI20_PB_MTT, /* 016 MULTICAST ADDRESS TABLE STARTING ADDRESS */ + NI20_PB_RS3, /* 017 RESERVED */ + NI20_PB_ER0, /* 020 KLNI ERROR LOGOUT 0 */ + NI20_PB_ER1, /* 021 KLNI ERROR LOGOUT 1 */ + NI20_PB_LAD, /* 022 ADDRESS OF CHANNEL LOGOUT WORD 1 */ + NI20_PB_CLO, /* 023 CONTENTS OF CHANNEL LOGOUT WORD 1 */ + NI20_PB_PBA, /* 024 PORT CONTROL BLOCK BASE ADDRESS */ + NI20_PB_PIA, /* 025 PI LEVEL ASSIGNMENT */ + NI20_PB_IVA, /* 026 INTERRUPT VECTOR ASSIGNMENT */ + NI20_PB_CCW, /* 027 CHANNEL COMMAND WORD */ + NI20_PB_RCB /* 030 POINTER TO READ COUNTERS BUFFER */ +}; + +/* Protocol Type Table (PTT) defs */ + +#define NI20_PT_FLD 0 /* Word offset for various fields */ +# define NI20_PTF_ENA 0400000 /* B0 - Enable this protocol */ +# define NI20_PTF_MBZ 0377774 /* B1-15 MBZ */ +# define NI20_PTF_TYP 03777760 /* B16-31 LH+RH: Protocol Type */ +# define NI20_PTF_FRE 01 /* B35 - Software? Entry "free" */ +#define NI20_PT_FRQ 1 /* Wd offset for Free Queue header */ +#define NI20_PT_VIR 2 /* Software (exec virt addr of FRQ) */ +#define NI20_PT_LEN 3 /* Length of a PTT entry */ + +/* Multi-Cast Address Table (MCAT) defs */ + +#define NI20_MT_HAD 0 /* High 4 bytes of address */ +#define NI20_MT_LAD 1 /* Low 2 bytes (in LH) */ +# define NI20_MTF_ENA 01 /* B35 - Enable bit in LAD */ +#define NI20_MT_LEN 2 /* Length of a MCAT entry */ + + +/* Queue defs. +** Note T20 waits up to 5 seconds for an interlock word to free up; +** however, during that time it does nothing but spin. +** +** If a queue is empty, the header links both point to the FLINK word. +** If a queue entry is the first, its BLINK points to the header FLINK. +** If a queue entry is the last, its FLINK points to the header FLINK. +** Thus, link manipulation can take place by treating the list as +** completely composed of queue entries, even though one of the +** "entries" is actually part fo the queue header. +*/ + + /* Queue Header - word offsets */ +#define NI20_QH_IWD 0 /* Interlock Word */ +#define NI20_QH_FLI 1 /* Forward Link */ +#define NI20_QH_BLI 2 /* Backward Link */ +#define NI20_QH_QES 3 /* Queue entry size (LEN in T20 code) */ + + /* Queue Entry - word offsets */ +#define NI20_QE_FLI 0 /* Forward Link */ +#define NI20_QE_BLI 1 /* Backward Link */ +#define NI20_QE_VIR 2 /* Software - Exec virt addr of entry */ +#define NI20_QE_OPC 3 /* Operation Code */ + + +/* Command Entry definitions */ + +#define NI20_CM_FLI NI20_QE_FLI /* Forward link */ +#define NI20_CM_BLI NI20_QE_BLI /* Backward Link */ +#define NI20_CM_VAD NI20_QE_VIR /* Software */ +#define NI20_CM_CMD NI20_QE_OPC /* Command Operation Code etc */ + + /* Error Status byte */ +# define NI20_CMF_STSB 0776000 /* Status byte */ +# define NI20_CMF_SRI 0200000 /* 0=Receive, 1=Send */ +# define NI20_CMF_ERR 0174000 /* Error Type */ +# define NI20_CMF_ERF 02000 /* 1=Error status meaningful (else all MBZ) */ + + /* Flags byte - for SEND DGM unless otherwise specified */ +# define NI20_CMF_PAC 01000 /* Packing; 0=Indust-Compatible, 1=Reserved */ +# define NI20_CMF_CRC 0400 /* CRC appended (NB: unused by T10/T20!) */ +# define NI20_CMF_PAD 0200 /* Add len, pad if nec (T20: "unused"?) */ + /* 0100 reserved */ +# define NI20_CMF_BSD 040 /* Using Buffer Seg Descr format */ + /* 020 reserved */ + /* 010 reserved */ +# define NI20_CMF_CLR 010 /* Clear counters (Read-Counters cmd only) */ +# define NI20_CMF_RSP 04 /* Response requested (any cmd) */ + + /* Opcode byte */ +# define NI20_CMF_OPC 03770000 /* LH+RH: command opcode */ + + /* "Time Domain Reflectometry" value. + ** For errors 00 and 01, B26-35 of the cmd word are set to + ** the time, in 100ns ticks, from the start of transmission until + ** the error event was detected by the hardware. + */ +# define NI20_CMF_TDR 01777 /* TDR */ + + +/* Command Opcodes */ + +#define NI20_OP_FLS 0 /* Flush commands -- must be illegal opcode */ +#define NI20_OP_SND 1 /* Send Datagram */ +#define NI20_OP_LDM 2 /* Load Multicast Address Table */ +#define NI20_OP_LDP 3 /* Load Protocol Type Table */ +#define NI20_OP_RCC 4 /* Read and Clear Counters */ +#define NI20_OP_RCV 5 /* Datagram Received */ +#define NI20_OP_WPL 6 /* Write PLI */ +#define NI20_OP_RPL 7 /* Read PLI */ +#define NI20_OP_RSI 8 /* Read Station Information */ +#define NI20_OP_WSI 9 /* Write Station Information */ + +/* Error code definitions +** +** Returned in NI20_CMF_ERR field of a response. Note the field +** is only meaningful if the NI20_CMF_ERF bit (low bit) is set. +*/ +#define NI20_ERR_EXC 0 /* Excessive collisions */ +#define NI20_ERR_CCF 01 /* Carrier check failed */ +#define NI20_ERR_CDF 02 /* Collision detect check failed */ +#define NI20_ERR_SCI 03 /* Short circuit */ +#define NI20_ERR_OCI 04 /* Open circuit */ +#define NI20_ERR_FTL 05 /* Frame too long */ +#define NI20_ERR_RFD 06 /* Remote failure to defer */ +#define NI20_ERR_BCE 07 /* Block check error (CRC error) */ +#define NI20_ERR_FER 010 /* Framing error */ +#define NI20_ERR_DOV 011 /* Data overrun */ +#define NI20_ERR_UPT 012 /* Unrecognized protocol type?!? */ +#define NI20_ERR_FTS 013 /* Frame too short */ + +#define NI20_ERR_SCE 027 /* Spurious channel error */ +#define NI20_ERR_CER 030 /* Channel error (WC <> 0) */ +#define NI20_ERR_QLV 031 /* Queue length violation */ +#define NI20_ERR_IPL 032 /* Illegal PLI function */ +#define NI20_ERR_URC 033 /* Unrecognized command */ +#define NI20_ERR_BLV 034 /* Buffer length violation */ +#define NI20_ERR_RSV 035 /* Reserved */ +#define NI20_ERR_TBP 036 /* Xmit buffer parity error */ +#define NI20_ERR_INT 037 /* Internal error */ + + +/* SNDDG (1= Send Datagram) command definitions */ + +#define NI20_CM_SNTXL NI20_CM_CMD+1 /* Word holding text length */ +# define NI20_SNF_TXL 0177777 /* B20-35 Text length in bytes */ +#define NI20_CM_SNPTY NI20_CM_CMD+2 /* Word holding protocol type */ +# define NI20_SNF_PTY 03777740 /* B16-31 Protocol type */ +#define NI20_CM_SNFRQ NI20_CM_CMD+3 /* Word holding FREEQ header addr */ + /* Note: KLNI.MEM is silent on the use of the FRQ word, and + ** the T20 PHYKNI code never references it! + */ +#define NI20_CM_SNHAD NI20_CM_CMD+4 /* High order part of E/N address */ +#define NI20_CM_SNLAD NI20_CM_CMD+5 /* Low order part */ + +#define NI20_CM_SNDTA NI20_CM_CMD+6 /* Non-BSD: 1st word of data */ +#define NI20_CM_SNBBA NI20_CM_CMD+6 /* BSD: Physical BSD base address */ + /* Note: T20 always uses BSD format for sending */ + + +/* BSD (Buffer-Seg-Descriptor) definitions */ + +#define NI20_BD_HDR 0 /* Packing mode & data seg addr */ +# define NI20_BDF_PAC 04000 /* B6 - Packing mode 0=indust-compat, 1=rsvd */ + /* Note T20 def uses field B6-7 ?!? */ +# define NI20_BDF_SBA 077777777 /* B12-35 24-bit phys seg base addr */ +#define NI20_BD_NXA 1 /* Next BSD address (24-bit) */ +#define NI20_BD_SLN 2 /* Seg length */ +# define NI20_BDF_SLN 0177777 /* B20-35 16-bit segment length in bytes */ +#define NI20_BD_RES 3 /* Reserved for software */ + + +/* LDMCAT (2= Load Multicast Address Table) Command definitions +** (no additional queue data) +*/ + +/* LDPTT (3= Load Protocol Type Table) Command definitions +** (no additional queue data) +*/ + +/* RCCNT (4= Read/Clear Counters) Command definitions +** (no additional queue data) +** RCCNT response: +*/ +enum ni20_rccntr { + NI20_RC_BR=0, /* BYTES RECEIVED */ + NI20_RC_BX, /* BYTES TRANSMITTED */ + NI20_RC_FR, /* FRAMES RECEIVED */ + NI20_RC_FX, /* FRAMES TRANSMITTED */ + NI20_RC_MCB, /* MULTICAST BYTES RECEIVED */ + NI20_RC_MCF, /* MULTICAST FRAMES RECEIVED */ + NI20_RC_FXD, /* FRAMES XMITTED, INITIALLY DEFERRED */ + NI20_RC_FXS, /* FRAMES XMITTED, SINGLE COLLISION */ + NI20_RC_FXM, /* FRAMES XMITTED, MULTIPLE COLLISIONS */ + NI20_RC_XF, /* TRANSMIT FAILURES */ + NI20_RC_XFM, /* TRANSMIT FAILURE BIT MASK */ +# define NI20_RCF_LOC 04000 /* B24 - LOSS OF CARRIER */ +# define NI20_RCF_XBP 02000 /* B25 - XMIT BUFFER PARITY ERROR */ +# define NI20_RCF_RFD 01000 /* B26 - REMOTE FAILURE TO DEFER */ +# define NI20_RCF_XFL 0400 /* B27 - XMITTED FRAME TOO LONG */ +# define NI20_RCF_OC 0200 /* B28 - OPEN CIRCUIT */ +# define NI20_RCF_SC 0100 /* B29 - SHORT CIRCUIT */ +# define NI20_RCF_CCF 040 /* B30 - COLLISION DETECT CHECK FAILED */ +# define NI20_RCF_EXC 020 /* B31 - EXCESSIVE COLLISIONS */ + + NI20_RC_CDF, /* CARRIER DETECT CHECK FAILED */ + NI20_RC_RF, /* RECEIVE FAILURES */ + NI20_RC_RFM, /* RECEIVE FAILURE BIT MASK */ +# define NI20_RCF_FLE 0400 /* B27 - FREE LIST PARITY ERROR */ +# define NI20_RCF_NFB 0200 /* B28 - NO FREE BUFFERS */ +# define NI20_RCF_FTL 0100 /* B29 - FRAME TOO LONG */ +# define NI20_RCF_FER 040 /* B30 - FRAMING ERROR */ +# define NI20_RCF_BCE 020 /* B31 - BLOCK CHECK ERROR */ + + NI20_RC_DUN, /* DISCARDED UNKNOWN */ + NI20_RC_D01, /* DISCARDED POSITION 1 */ + NI20_RC_D02, /* DISCARDED POSITION 2 */ + NI20_RC_D03, /* DISCARDED POSITION 3 */ + NI20_RC_D04, /* DISCARDED POSITION 4 */ + NI20_RC_D05, /* DISCARDED POSITION 5 */ + NI20_RC_D06, /* DISCARDED POSITION 6 */ + NI20_RC_D07, /* DISCARDED POSITION 7 */ + NI20_RC_D08, /* DISCARDED POSITION 8 */ + NI20_RC_D09, /* DISCARDED POSITION 9 */ + NI20_RC_D10, /* DISCARDED POSITION 10 */ + NI20_RC_D11, /* DISCARDED POSITION 11 */ + NI20_RC_D12, /* DISCARDED POSITION 12 */ + NI20_RC_D13, /* DISCARDED POSITION 13 */ + NI20_RC_D14, /* DISCARDED POSITION 14 */ + NI20_RC_D15, /* DISCARDED POSITION 15 */ + NI20_RC_D16, /* DISCARDED POSITION 16 */ + NI20_RC_UFD, /* UNRECOGNIZED FRAME DEST */ + NI20_RC_DOV, /* DATA OVERRUN */ + NI20_RC_SBU, /* SYSTEM BUFFER UNAVAILABLE */ + NI20_RC_UBU, /* USER BUFFER UNAVAILABLE */ + NI20_RC_RS0, /* PLI REG RD PAR ERROR,,PLI PARITY ERROR */ + NI20_RC_RS1, /* MOVER PARITY ERROR,,CBUS PARITY ERROR */ + NI20_RC_RS2, /* EBUS PARITY ERROR,,EBUS QUE PARITY ERROR */ + NI20_RC_RS3, /* CHANNEL ERROR,,SPUR CHANNEL ERROR */ + NI20_RC_RS4, /* SPUR XMIT ATTN ERROR,,CBUS REQ TIMOUT ERROR */ + NI20_RC_RS5, /* EBUS REQ TIMEOUT ERROR,,CSR GRNT TIMEOUT ERROR */ + NI20_RC_RS6, /* USED BUFF PARITY ERROR,,XMIT BUFF PARITY ERROR */ + NI20_RC_RS7, /* RESERVED FOR UCODE */ + NI20_RC_RS8, /* RESERVED FOR UCODE */ + NI20_RCLEN /* # of counters */ +}; + + +/* DGRCV (5= Datagram Received) Command definitions +*/ + +#define NI20_CM_RDSIZ NI20_CM_CMD+1 /* Word holding text length */ +# define NI20_RDF_SIZ 0177777 /* B20-35 Text len (+4 CRC) in bytes */ +#define NI20_CM_RDDHA NI20_CM_CMD+2 /* High order destination addr */ +#define NI20_CM_RDDLA NI20_CM_CMD+3 /* Low order part */ +#define NI20_CM_RDSHA NI20_CM_CMD+4 /* High order source addr */ +#define NI20_CM_RDSLA NI20_CM_CMD+5 /* Low order part */ +#define NI20_CM_RDPTY NI20_CM_CMD+6 /* Word holding protocol type */ +# define NI20_RDF_PTY 03777740 /* B16-31 Protocol type */ +#define NI20_CM_RDPBA NI20_CM_CMD+7 /* Physical BSD base address */ + + +/* WRTPLI (6= Write Port/Link Interface) Command definitions +*/ + +/* RDPLI (7= Read Port/Link Interface) Command definitions +*/ + +/* RDNSA (8= Read NI Station Address) Command definitions +** No extra queue entry words for command, but response has: +*/ +#define NI20_CM_RSHAD NI20_CM_CMD+1 /* High order station E/N addr */ +#define NI20_CM_RSLAD NI20_CM_CMD+2 /* Low order */ +#define NI20_CM_RSFLG NI20_CM_CMD+3 /* Misc flags */ +# define NI20_RSF_FLGMSK 017 /* All flags */ +# define NI20_RSF_CRC 010 /* Allow receipt of frames with CRC errs */ +# define NI20_RSF_PMC 04 /* Receive all Multicast frames */ +# define NI20_RSF_H40 02 /* Enable heartbeat detection by transceiver */ +# define NI20_RSF_PRM 01 /* Promiscuous mode - receive all frames */ +#define NI20_CM_RSVER NI20_CM_CMD+4 /* Version & table limits */ +# define NI20_RSF_UCV 03770000 /* LH+RH: IPA20-L port Ucode version */ +# define NI20_RSF_NMC 07700 /* # of MCAT entries allowed */ +# define NI20_RSF_NPT 077 /* # of PTT entries allowed */ + + +/* WRTNSA (9= Write NI Station Address) Command definitions +*/ +#define NI20_CM_WSHAD NI20_CM_RSHAD /* High order station E/N addr */ +#define NI20_CM_WSLAD NI20_CM_RSLAD /* Low order */ +#define NI20_CM_WSFLG NI20_CM_RSFLG /* Misc flags */ + /* See NI20_RSF_ defs */ +#define NI20_CM_WSRTY NI20_CM_CMD+4 /* Retry count */ +# define NI20_WSF_RTY 07777 /* Retry count (default 5) */ + /* T20 sets this to 16. */ + +#endif /* ifndef NI20_INCLUDED */ diff --git a/src/dvrh11.c b/src/dvrh11.c new file mode 100644 index 0000000..00ff040 --- /dev/null +++ b/src/dvrh11.c @@ -0,0 +1,1018 @@ +/* DVRH11.C - Emulates RH11 controller for KS10 +*/ +/* $Id: dvrh11.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1997, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dvrh11.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#include "klh10.h" + +#if !KLH10_DEV_RH11 && CENV_SYS_DECOSF + /* Stupid gubbish needed to prevent OSF/1 AXP compiler from + ** halting merely because compiled file is empty! + */ +static int decosfcclossage; +#endif + +#if KLH10_DEV_RH11 /* Moby conditional for entire file */ + +#include /* For size_t etc */ +#include +#include +#include + +#include "kn10def.h" /* This includes OSD defs */ +#include "kn10dev.h" +#include "dvuba.h" +#include "dvrh11.h" +#include "dvrh20.h" /* Temp: for RHR ext reg defs */ +#include "dvrpxx.h" /* Temp: for RH_DTNBA def */ +#include "prmstr.h" /* For parameter parsing */ + +#ifdef RCSID + RCSID(dvrh11_c,"$Id: dvrh11.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +struct rh11 { + struct device rh_dv; /* Generic 10 device structure */ + + /* RH11-specific vars */ + + int rh_no; /* RH number - use UBA # here */ + + /* RH11 internal registers */ + uint18 rh_cs1; /* Only RH11 bits are set here, no drive bits */ + uint18 rh_cs2; + uint18 rh_wc; + uint18 rh_ba; + + /* Drive bindings & selection */ + + int rh_ds; /* Drive select (0-7) */ + struct device *rh_dsptr; + + int rh_attn; /* ATTN summary for all drives */ + struct device *rh_drive[8]; + uint18 rh_dt[8]; /* Drive type for each known drive */ + + /* Pseudo "Data Channel" vars */ + int rh_dcwcnt; /* DC current xfer word count */ + paddr_t rh_dcbuf; /* DC current xfer data buffer pointer */ + int rh_bcnt; /* Initial block count */ + int rh_dcrev; /* TRUE if doing reverse data xfer */ + int rh_dcwrt; /* TRUE if doing device write */ + + w10_t rh_dcccw; /* DC current cmd word */ + int rh_eptoff; /* Offset of channel area in EPT */ + paddr_t rh_clp; /* DC "PC" - Current Command List Pointer */ + uint18 rh_dcsts; /* DC status flag bits (LH) */ + int rh_dchlt; /* TRUE if halt when current wordcount done */ + +}; + +static int nrh11s = 0; +/* static */ struct rh11 dvrh11[RH11_NSUP]; + +#define RHDEBUG(rh) ((rh)->rh_dv.dv_debug) +#define RHDBF(rh) ((rh)->rh_dv.dv_dbf) + + +/* Function predecls */ + +static int rh11_conf(FILE *f, char *s, struct rh11 *rh); + +/* Functions provided to device vector */ + +static int rh11_bind(struct device *d, FILE *of, struct device *slv, int num); +static int rh11_init(struct device *d, FILE *of); +static void rh11_reset(struct device *d); +static dvureg_t rh11_pivec(struct device *d); +static dvureg_t rh11_read(struct device *d, uint18 addr); +static void rh11_write(struct device *d, uint18 addr, dvureg_t val); + +/* Functions provided to slave device */ + +static void rh11_attn(struct device *drv, int on); +static void rh11_drerr(struct device *drv); +static int rh11_iobeg(struct device *drv, int wflg); +static int rh11_iobuf(struct device *drv, int wc, vmptr_t *avp); +static void rh11_ioend(struct device *drv, int bc); + +/* Completely internal functions */ + +static void rh_picheck(struct rh11 *rh); +static void rh_pi(struct rh11 *rh); +static void rh_clrattn(struct rh11 *rh, int msk); +static void rh_clear(struct rh11 *rh); + +static void rhdc_clear(struct rh11 *rh); +static int rhdc_ccwget(struct rh11 *rh); + +/* Configuration Parameters */ + +#define DVRH11_PARAMS \ + prmdef(RH11P_DBG, "debug"), /* Initial debug value */\ + prmdef(RH11P_BR, "br"), /* BR priority */\ + prmdef(RH11P_VEC, "vec"), /* Interrupt vector */\ + prmdef(RH11P_ADDR,"addr") /* Unibus address */ + +enum { +# define prmdef(i,s) i + DVRH11_PARAMS +# undef prmdef +}; + +static char *rh11prmtab[] = { +# define prmdef(i,s) s + DVRH11_PARAMS +# undef prmdef + , NULL +}; + +/* RH11_CONF - Parse configuration string and set defaults. +** At this point, device has just been created, but not yet bound +** or initialized. +** NOTE that some strings are dynamically allocated! Someday may want +** to clean them up nicely if config fails or device is uncreated. +*/ +static int +rh11_conf(FILE *f, char *s, struct rh11 *rh) +{ + int i, ret = TRUE; + struct prmstate_s prm; + char buff[200]; + long lval; + + /* First set defaults for all configurable parameters + Unfortunately there's currently no way to access the UBA # that + we're gonna be bound to, otherwise could set up defaults. Later + fix this by giving rh11_create() a ptr to an arg structure, etc. + */ + DVDEBUG(rh) = FALSE; + + prm_init(&prm, buff, sizeof(buff), + s, strlen(s), + rh11prmtab, sizeof(rh11prmtab[0])); + while ((i = prm_next(&prm)) != PRMK_DONE) { + switch (i) { + case PRMK_NONE: + fprintf(f, "Unknown RH11 parameter \"%s\"\n", prm.prm_name); + ret = FALSE; + continue; + case PRMK_AMBI: + fprintf(f, "Ambiguous RH11 parameter \"%s\"\n", prm.prm_name); + ret = FALSE; + continue; + default: /* Handle matches not supported */ + fprintf(f, "Unsupported RH11 parameter \"%s\"\n", prm.prm_name); + ret = FALSE; + continue; + + case RH11P_DBG: /* Parse as true/false boolean or number */ + if (!prm.prm_val) /* No arg => default to 1 */ + DVDEBUG(rh) = 1; + else if (!s_tobool(prm.prm_val, &DVDEBUG(rh))) + break; + continue; + + case RH11P_BR: /* Parse as octal number */ + if (!prm.prm_val || !s_tonum(prm.prm_val, &lval)) + break; + if (lval < 4 || lval > 7) { + fprintf(f, "RH11 BR must be one of 4,5,6,7\n"); + ret = FALSE; + } else + rh->rh_dv.dv_brlev = lval; + continue; + + case RH11P_VEC: /* Parse as octal number */ + if (!prm.prm_val || !s_tonum(prm.prm_val, &lval)) + break; + if (lval < 4 || lval > 0400 || (lval&03)) { + fprintf(f, "RH11 VEC must be valid multiple of 4\n"); + ret = FALSE; + } else + rh->rh_dv.dv_brvec = lval; + continue; + + case RH11P_ADDR: /* Parse as octal number */ + if (!prm.prm_val || !s_tonum(prm.prm_val, &lval)) + break; + if (lval < (RH11R_N<<1) || (lval&037)) { + fprintf(f, "RH11 ADDR must be valid Unibus address\n"); + ret = FALSE; + } else + rh->rh_dv.dv_addr = lval; + continue; + + } + ret = FALSE; + fprintf(f, "RH11 param \"%s\": ", prm.prm_name); + if (prm.prm_val) + fprintf(f, "bad value syntax: \"%s\"\n", prm.prm_val); + else + fprintf(f, "missing value\n"); + } + + /* Param string all done, do followup checks or cleanup */ + if (!rh->rh_dv.dv_brlev || !rh->rh_dv.dv_brvec || !rh->rh_dv.dv_addr) { + fprintf(f, "RH11 missing one of BR, VEC, ADDR params\n"); + ret = FALSE; + } + /* Set 1st invalid addr */ + rh->rh_dv.dv_aend = rh->rh_dv.dv_addr + (RH11R_N * 2); + + return ret; +} + +/* RH11 interface routines to KLH10 */ + +struct device * +dvrh11_create(FILE *f, char *s) +{ + register struct rh11 *rh; + + /* Allocate a RH11 device structure */ + if (nrh11s >= RH11_NSUP) { + fprintf(f, "Too many RH11s, max: %d\n", RH11_NSUP); + return NULL; + } + rh = &dvrh11[nrh11s++]; /* Pick unused RH11 */ + + /* Various initialization stuff */ + memset((char *)rh, 0, sizeof(*rh)); + + iodv_setnull(&rh->rh_dv); /* Init as null device */ + + rh->rh_dv.dv_dflags = DVFL_CTLR; + rh->rh_dv.dv_bind = rh11_bind; /* Controller, so can bind. */ + rh->rh_dv.dv_init = rh11_init; /* Set up own post-bind init */ + rh->rh_dv.dv_reset = rh11_reset; /* System reset (clear stuff) */ + + /* Unibus stuff */ + rh->rh_dv.dv_pivec = rh11_pivec; /* Return PI vector */ + rh->rh_dv.dv_read = rh11_read; /* Read unibus register */ + rh->rh_dv.dv_write = rh11_write; /* Write unibus register */ + + /* Configure from parsed string and remember for init + */ + if (!rh11_conf(f, s, rh)) + return NULL; + + return &rh->rh_dv; +} + +static int +rh11_init(struct device *d, FILE *of) +{ + register struct rh11 *rh = (struct rh11 *)d; + + /* Clear RH-internal registers */ + rh->rh_cs1 = 0; + rh->rh_cs2 = 0; + rh->rh_wc = 0; + rh->rh_ba = 0; + rh->rh_attn = 0; /* Clear ATTN summary */ + rh->rh_dsptr = NULL; /* No drive */ + + rh->rh_no = rh->rh_dv.dv_uba->ubn; /* Find Unibus # we're on */ + + return TRUE; +} + +static int +rh11_bind(register struct device *d, + FILE *of, + register struct device *slv, + int num) +{ + register struct rh11 *rh = (struct rh11 *)d; + + if (0 <= num && num < 8) ; + else { + if (of) fprintf(of, "RH11 can only bind up to 8 drives (#%d invalid).\n", num); + return FALSE; + } + + /* Backpointer and device # (dv_ctlr, dv_num) are already set up. */ + slv->dv_attn = rh11_attn; /* Set attention handler */ + slv->dv_drerr = rh11_drerr; /* Set exception handler */ + slv->dv_iobeg = rh11_iobeg; /* IO xfer beg */ + slv->dv_iobuf = rh11_iobuf; /* IO xfer buffer setup */ + slv->dv_ioend = rh11_ioend; /* IO xfer end */ + + rh->rh_drive[num] = slv; + rh->rh_dt[num] = 0; /* Drive not yet inited, so can't ask it */ + + return TRUE; +} + +/* RH11_RESET - Clear controller and drives +** Also does equivalent of asserting Massbus INIT, by invoking +** the reset routine for all drives. +*/ +static void +rh11_reset(struct device *d) +{ + register struct rh11 *rh = (struct rh11 *)d; + register int i; + + rh_clear(rh); /* Clear RH-only controller stuff */ + + /* Clear all drives for this controller */ + for (i = 0; i < 8; ++i) + if (rh->rh_drive[i]) + (*(rh->rh_drive[i]->dv_reset))(rh->rh_drive[i]); +} + +/* PI: Return interrupt vector */ + +static dvureg_t +rh11_pivec(register struct device *d) +{ + (*d->dv_pifun)(d, 0); /* Turn off interrupt request */ + return d->dv_brvec; /* Return vector to use */ +} + + +/* Table for mapping RH11 registers into RH20-drive registers. +** Note certain RH regs are mapped into special internal-reg numbers. +** Also note that one RH11 reg address (BUF) has no counterpart and is mapped +** into a no-op read-only reg. +*/ +#define RHRX_WC RHR_N+1 +#define RHRX_BA RHR_N+2 +#define RHRX_CS2 RHR_N+3 + +static int rh11regmap[RH11R_N] = { + RHR_CSR, /* 0 RH11R_CS1 - (RH/DR) CTRL AND STATUS 1. */ + RHRX_WC, /* 1 RH11R_WC - (RH) WORD COUNT. */ + RHRX_BA, /* 2 RH11R_BA - (RH) UNIBUS ADDRESS. */ + RHR_BAFC, /* 3 RH11R_ADR - (DR) DESIRED ADDRESS. */ + RHRX_CS2, /* 4 RH11R_CS2 - (RH) CTRL AND STATUS 2. */ + RHR_STS, /* 5 RH11R_STS - (DR) DRIVE STATUS. */ + RHR_ER1, /* 6 RH11R_ER1 - (DR) ERROR 1. */ + RHR_ATTN, /* 7 RH11R_ATN - (DR*) ATTENTION SUMMARY. */ + RHR_LAH, /* 010 RH11R_LAH - (DR) LOOK AHEAD. */ + RHR_SN, /* 011 RH11R_BUF - (DR) DATA BUFFER. */ + RHR_MNT, /* 012 RH11R_MNT - (DR) MAINTENANCE. */ + RHR_DT, /* 013 RH11R_TYP - (DR) DRIVE TYPE. */ + RHR_SN, /* 014 RH11R_SER - (DR) SERIAL NUMBER. */ + RHR_OFTC, /* 015 RH11R_OFS - (DR) OFFSET. */ + RHR_DCY, /* 016 RH11R_CYL - (DR) DESIRED CYLINDER. */ + RHR_CCY, /* 017 RH11R_CCY - (DR) CURRENT CYLINDER */ + RHR_ER2, /* 020 RH11R_ER2 - (DR) ERROR 2 */ + RHR_ER3, /* 021 RH11R_ER3 - (DR) ERROR 3 */ + RHR_EPOS, /* 022 RH11R_POS - (DR) ECC POSITION. */ + RHR_EPAT /* 023 RH11R_PAT - (DR) ECC PATTERN. */ +}; + +static char *rh11regnam[RH11R_N] = { + "CS1", /* 0 RH11R_CS1 - (RH/DR) CTRL AND STATUS 1. */ + "WC", /* 1 RH11R_WC - (RH) WORD COUNT. */ + "BA", /* 2 RH11R_BA - (RH) UNIBUS ADDRESS. */ + "ADR", /* 3 RH11R_ADR - (DR) DESIRED ADDRESS. */ + "CS2", /* 4 RH11R_CS2 - (RH) CTRL AND STATUS 2. */ + "STS", /* 5 RH11R_STS - (DR) DRIVE STATUS. */ + "ER1", /* 6 RH11R_ER1 - (DR) ERROR 1. */ + "ATN", /* 7 RH11R_ATN - (DR*) ATTENTION SUMMARY. */ + "LAH", /* 010 RH11R_LAH - (DR) LOOK AHEAD. */ + "BUF", /* 011 RH11R_BUF - (DR) DATA BUFFER. */ + "MNT", /* 012 RH11R_MNT - (DR) MAINTENANCE. */ + "TYP", /* 013 RH11R_TYP - (DR) DRIVE TYPE. */ + "SER", /* 014 RH11R_SER - (DR) SERIAL NUMBER. */ + "OFS", /* 015 RH11R_OFS - (DR) OFFSET. */ + "CYL", /* 016 RH11R_CYL - (DR) DESIRED CYLINDER. */ + "CCY", /* 017 RH11R_CCY - (DR) CURRENT CYLINDER */ + "ER2", /* 020 RH11R_ER2 - (DR) ERROR 2 */ + "ER3", /* 021 RH11R_ER3 - (DR) ERROR 3 */ + "POS", /* 022 RH11R_POS - (DR) ECC POSITION. */ + "PAT" /* 023 RH11R_PAT - (DR) ECC PATTERN. */ +}; + + +static dvureg_t +rh11_read(struct device *d, register uint18 addr) +{ + register struct rh11 *rh = (struct rh11 *)d; + register int reg; + register uint32 dregval; + register dvureg_t val; + + reg = (addr - rh->rh_dv.dv_addr) >> 1; + if (reg < 0 || reg >= RH11R_N) { + /* In theory ought to generate illegal IO register page-fail here, + but this should never happen - all addresses for this device + are being handled. + */ + panic("rh11_read: Unknown register %o", addr); + } + + switch (reg) { + + /* Controller register stuff */ + case RH11R_CS2: val = rh->rh_cs2; break; /* CTRL AND STATUS 2 */ + case RH11R_WC: val = rh->rh_wc; break; /* WORD COUNT */ + case RH11R_BA: val = rh->rh_ba; break; /* UNIBUS ADDRESS */ + case RH11R_ATN: val = rh->rh_attn; break; /* ATTENTION SUMMARY */ + + case RH11R_CS1: /* CTRL AND STATUS 1 - special combo of RH and drive */ + /* Special handling is required for this one as it combines + both the RH and a selected drive. If no drive is selected, + then this access attempt must cause a Massbus Parity Error! + */ + if (!rh->rh_dsptr) { + rh->rh_cs2 |= RH_YNED; /* Non-existent drive - set on ref */ + rh->rh_cs1 |= RH_XMCP; /* Shd we trigger PI? For now, no */ + val = rh->rh_cs1; + break; + } + + if ((MASK16 < (dregval = + (*rh->rh_dsptr->dv_rdreg)(rh->rh_dsptr, RHR_CSR)))) { + dregval = 0; + rh->rh_cs1 |= RH_XMCP; /* Shd we trigger PI? For now, no */ + } + val = (rh->rh_cs1 | (dregval & RH_CS1_DRIVE)); + break; + + default: + /* Get value from drive. */ + if (rh->rh_dsptr && (MASK16 >= (dregval = + (*rh->rh_dsptr->dv_rdreg)(rh->rh_dsptr, rh11regmap[reg])))) { + val = dregval; + break; + } + /* Non-existent drive or drive register access error. + ** Unclear what should be done for this case; IO page fail? + */ + if (RHDEBUG(rh)) { + fprintf(RHDBF(rh), "[rh11_read: %o.%o RAE for %s: ", + rh->rh_no, rh->rh_ds, rh11regnam[reg]); + if (!rh->rh_dsptr) + fprintf(RHDBF(rh), "no drive]\r\n"); + else + fprintf(RHDBF(rh), "%lo]\r\n", (long)dregval); + } + rh->rh_cs1 |= RH_XMCP; /* Shd we trigger PI? For now, no */ + return 0; + } + + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "[rh11_read: %o.%o %s: %lo]\r\n", + rh->rh_no, rh->rh_ds, rh11regnam[reg], (long)val); + return val; +} + +/* Note from T20: + +THE RH11 REQUIRES THAT A CLEAR INSTRUCTION (setting RH_YCLR in CS2) BE +FOLLOWED IMMEDIATELY BY A SELECTION OF AN EXISTANT UNIT NUMBER +OTHERWISE ANY ATTEMPT TO ACCESS A REGISTER THAT IS NOT IN THE RH (CS1) +WILL CAUSE MASSBUS PARITY ERRORS. + +- Apparently CS2 CLEAR cannot simultaneously do a drive select; it causes no + drive to be selected, and another CS2 write without the clear bit + is required. +- If no drive is selected (or an invalid one is picked) apparently RH_YNED + is not set until an actual attempt is made to access (read or write) + a drive register (of which CS1 counts as one!). + +- In ER1, T20 only checks RH_1PAR (Control Bus Parity Error) and + RH_1AOE (Address Overflow / Frame Count Error) + +*/ + +static void +rh11_write(struct device *d, uint18 addr, register dvureg_t val) +{ + register struct rh11 *rh = (struct rh11 *)d; + register int reg; + + reg = (addr - rh->rh_dv.dv_addr) >> 1; + if (reg < 0 || reg >= RH11R_N) { + /* In theory ought to generate illegal IO register page-fail here, + but this should never happen - all addresses for this device + are being handled. + */ + panic("rh11_write: Unknown register %o", addr); + } + + val &= MASK16; + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "[rh11_write: %o.%o %s: %lo]\r\n", + rh->rh_no, rh->rh_ds, rh11regnam[reg], (long)val); + switch (reg) { + + case RH11R_CS1: /* R/W CS1 CS1 Control/command */ + /* Set RH bits first */ + if (val & RH_XTRE) /* If turning off xfer err bit */ + rh->rh_cs1 &= ~RH_XTRE; /* do so. */ + rh->rh_cs1 &= ~(RH_XA17|RH_XA16|RH_XIE); /* Bits can set */ + rh->rh_cs1 |= (val & (RH_XA17|RH_XA16|RH_XIE)); /* Set em */ + + /* Now set drive bits */ + if (!rh->rh_dsptr) { + rh->rh_cs2 |= RH_YNED; /* Set Non-Existent Drive */ + } else { + /* Give command to drive! */ + if ((*rh->rh_dsptr->dv_wrreg)(rh->rh_dsptr, + RHR_CSR, (int)(val & RH_XCMD))) { + return; /* Success, all done here */ + } + goto wregerr; /* Ugh, report error */ + } + break; /* Go set RH_XMCP Control Bus Parity Error */ + + case RH11R_CS2: /* CTRL AND STATUS 2 */ + { + register int drvno = val & RH_YDSK; + + if (val & RH_YCLR) + rh_clear(rh); /* Clear controller (not drive) */ + /* Also turns off any PI request! */ + + rh->rh_cs2 = ((rh->rh_cs2)&~RH_YDSK) | drvno; + rh->rh_ds = drvno; + if (rh->rh_dsptr = rh->rh_drive[drvno]) { + rh->rh_cs2 &= ~RH_YNED; /* Drive exists */ + } else { + rh->rh_cs2 |= RH_YNED; /* Non-ex drive */ + } + return; + } + + case RH11R_WC: /* WORD COUNT */ + rh->rh_wc = val; + return; + + case RH11R_BA: /* UNIBUS ADDRESS */ + rh->rh_ba = val; + return; + + case RH11R_ATN: /* R/W [I2] ATN AS Attention Summary */ + rh_clrattn(rh, (int)val); /* Turn off ATTN for all bits set */ + return; + + default: /* All other registers are given to drive */ + if (rh->rh_dsptr && + (*rh->rh_dsptr->dv_wrreg)(rh->rh_dsptr, rh11regmap[reg], (int)val)) + return; /* External reg write succeeded */ + /* Error drops through */ + + wregerr: + if (RHDEBUG(rh)) { + fprintf(RHDBF(rh), "[rh11_write: %o.%o RAE for %s: ", + rh->rh_no, rh->rh_ds, rh11regnam[reg]); + if (!rh->rh_dsptr) + fprintf(RHDBF(rh), " (no drive)]"); + else + fprintf(RHDBF(rh), "]"); + } + break; + } + + + /* External reg write failed, set RH_XMCP and maybe do PI. */ + rh->rh_cs1 |= RH_XMCP; /* Set Control Bus Parity Error */ + if (rh->rh_cs1 & RH_XIE) { + rh_pi(rh); /* Trigger PI */ + } +} + +/* RH11 internal routines (internal registers etc) */ + +/* RH_CLEAR - Clear controller only (not drives) +*/ +static void +rh_clear(register struct rh11 *rh) +{ + if (rh->rh_dv.dv_pireq) /* If PI request outstanding, */ + (*rh->rh_dv.dv_pifun)(&rh->rh_dv, 0); /* clear it. */ + + /* Stop and flush any in-progress xfer? */ + + /* Need to figure out just which bits get cleared; for now, all RH bits */ + rh->rh_cs1 = RH_XRDY; /* CTRL AND STATUS 1 */ + rh->rh_cs2 = 0; /* CTRL AND STATUS 2 */ + rh->rh_wc = 0; + rh->rh_ba = 0; + + rh->rh_dsptr = NULL; /* No drive selected */ + rh->rh_ds = 0; +} + + +/* RH_PICHECK - Check RH11 conditions to see if PI should be attempted. +** Possible problem: RH11 has no "done" flag, so using this routine +** may not be a good idea as it could turn off the PI for command-done. +** RH_XSC is not the equivalent as it is only set for ATTN or error, +** not for transfer-done. +*/ +static void +rh_picheck(register struct rh11 *rh) +{ + /* If any possible interrupt bits are set */ + if (rh->rh_cs1 & (RH_XSC | RH_XTRE | RH_XMCP)) { + rh_pi(rh); + return; + } + /* Here, shouldn't be requesting PI, so if our request bit is set, + ** turn it off. + */ + if (rh->rh_dv.dv_pireq) { /* If set while shouldn't be, */ + (*rh->rh_dv.dv_pifun)(&rh->rh_dv, 0); /* Clear it! */ + } +} + + +/* RH11_PI - trigger PI for selected RH11. +** This could perhaps be an inline macro, but for now +** having it as a function helps debug. +*/ +static void +rh_pi(register struct rh11 *rh) +{ + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "[rh11_pi: %o]", rh->rh_dv.dv_brlev); + + if (rh->rh_dv.dv_brlev /* If have non-zero PIA */ + && !(rh->rh_dv.dv_pireq)) { /* and not already asking for PI */ + (*rh->rh_dv.dv_pifun)(&rh->rh_dv, rh->rh_dv.dv_brlev); /* then do it! */ + } +} + +/* RH_CLRATTN - Clear ATTN bit for all masked drives. +** Forces call even if it seems unnecessary, just to be safe. +*/ +static void +rh_clrattn(register struct rh11 *rh, int msk) +{ + register int i; + register struct device *drv; + + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "[rh11_clrattn: %o]", msk); + msk &= 0377; /* Allow only 8 bits for 8 drives */ + for (i = 0; msk; ++i, msk >>= 1) { + if ((msk & 01) && (drv = rh->rh_drive[i])) + (*drv->dv_wrreg)(drv, RHR_ATTN, 1); /* Tell drv to clr its ATTN */ + } +} + +/* The following rh11_ functions are all called by the drive via +** the controller/drive vectors. +*/ + +/* RH11_ATTN - Called by drive to assert or clear its attention bit. +** Note arg is pointer to drive, not controller. +*/ +static void +rh11_attn(register struct device *drv, int on) +{ + register struct rh11 *rh = (struct rh11 *)(drv->dv_ctlr); + register int rbit = (1 << drv->dv_num); /* Attention bit to use */ + + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "[rh11_attn: bit %o %s]", + rbit, on ? "on" : "off"); + + /* If turning ATN bit on, always interrupt if enabled, even if ATN + was already on. This is because it could have been previously + turned on while RH_XIE was off, and thus is not a reliable sign + of whether an interrupt is already pending. + */ + if (on) { + rh->rh_attn |= rbit; /* Add new attention bit! */ + rh->rh_cs1 |= RH_XSC; /* Assert Special Condition */ + if (rh->rh_cs1 & RH_XIE) /* If OK for attn to int */ + rh_pi(rh); /* then try to trigger PI! */ + } else { + if (rh->rh_attn & rbit) { + rh->rh_attn &= ~rbit; /* Turn off attention bit */ + if (!rh->rh_attn /* If no longer have any ATTNs */ + && (rh->rh_cs1 & RH_XSC)) { /* ensure Spec Cond is off */ + /* Changing Spec Cond state... may have to clean up PI */ + rh->rh_cs1 &= ~RH_XSC; /* ensure Spec Cond is off */ + if (rh->rh_dv.dv_pireq) /* If had PI request, */ + rh_picheck(rh); /* Sigh, must re-check stuff */ + } + } + } +} + + +/* RH11_DRERR - Called by drive to assert an exception error during an +** I/O transfer. +*/ +static void +rh11_drerr(register struct device *drv) +{ + register struct rh11 *rh = (struct rh11 *)(drv->dv_ctlr); + + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "[rh11_drerr]"); + + if (!(rh->rh_cs1 & RH_XTRE)) { + rh->rh_cs1 |= RH_XTRE; /* Assert Drive Transfer Error */ + rh_pi(rh); /* Then try to trigger PI */ + } + + /* If I/O transfer in progress, abort it gracefully */ + if (1) + rh11_ioend(drv, 1); /* Assume always have a block left */ +} + + +/* RH11_IOBEG - Called by drive to set up data channel just prior to +** starting an I/O xfer. +** Returns 0 if failure; drive should stop immediately and not +** invoke rh11_iobuf, but must still call rh11_ioend. +** Else returns # of block units (sectors or records) to do I/O for. +*/ +static int +rh11_iobeg(struct device *drv, int wflg) +{ + register struct rh11 *rh = (struct rh11 *)(drv->dv_ctlr); + register int nblks; + register int18 wc; + + rh->rh_dcwrt = wflg; /* Remember if writing */ + + /* Paranoia - consistency check */ + if (rh->rh_ds != drv->dv_num) { + panic("rh11_iobeg: drv/ctrlr mismatch: %d/%d", + rh->rh_ds, drv->dv_num); + } + + /* HACK HACK HACK - need to know drive type before we know whether + to return a meaningful block count. Block-addressed drives + are assumed to have 128 words per block (sector); later this + needs to be fixed up with better drive/controller communication. + */ + if (rh->rh_dt[drv->dv_num] == 0) { + rh->rh_dt[drv->dv_num] = (*drv->dv_rdreg)(drv, RHR_DT); + } + + /* Find word count in PDP10 words (rounds down) */ + wc = (-(rh->rh_wc | ~MASK16))>>1; /* Find # of PDP10 words */ + if (wc == 0) + nblks = 0; + else if (rh->rh_dt[drv->dv_num] & RH_DTNBA) + nblks = 1; /* Not a block device */ + else + nblks = (wc + 0177) >> 7; /* 128-wd block (sector) */ + + /* Do mapping and set up internal vars from the xfer registers */ + if (!rhdc_ccwget(rh)) { + rh->rh_cs1 |= RH_XTRE; /* Some kind of xfer error */ + rh_pi(rh); /* Try to trigger PI */ + return 0; /* Tell drive we failed */ + } + + rh->rh_cs1 &= ~RH_XRDY; /* Channel active now! */ + return rh->rh_bcnt = nblks; +} + + +/* RH11_IOBUF - Called by drive to query data channel to find source +** or dest buffer for I/O xfer. Can be called repeatedly for one xfer. +** Caller must provide # of words used from the last call (0 if none). +** In particular, final transfer MUST invoke this to tell the channel +** how many words were actually used. +** +** If returns 0, no data or space left. +** If returns +, OK, *avp NULL if skipping, else vmptr to mem. +** If returns -, wants to do reverse transfer. +** Not clear whether buffer pointer points to LAST word of +** block to write, or ONE PAST the last word. Assuming +** the former, based on DEC DC doc (p. DATA/2-7) desc of address. +*/ +static int +rh11_iobuf(struct device *drv, + register int wc, /* Max 11 bits of word count, so int OK */ + vmptr_t *avp) +{ + register struct rh11 *rh = (struct rh11 *)(drv->dv_ctlr); + + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "[rh11_iobuf: %d. => ", wc); + + /* If drive is updating our IO xfer status, do it. */ + if (wc) { + register uint18 ba; + + /* Special hacking needed for BA to update extension bits. + ** Code knows that A7 and A6 are contiguous. + */ + ba = rh->rh_ba + (wc << 2); /* Get updated BA value (byte addr) */ + if (ba > MASK16) { /* Overflowed 16 bits? */ + /* Add overflow bit in-place into extension bits */ + rh->rh_cs1 = (rh->rh_cs1 & ~(RH_XA17|RH_XA16)) + | ((rh->rh_cs1 + RH_XA16) & (RH_XA17|RH_XA16)); + ba &= MASK16; + } + rh->rh_ba = ba; + + if (wc < 0) { /* Verify direction consistent */ + if (!rh->rh_dcrev) + panic("rh11_iobuf: Neg wc for fwd xfer!"); + wc = -wc; /* Make positive */ + } else if (rh->rh_dcrev) + panic("rh11_iobuf: Pos wc for rev xfer!"); + if (wc > rh->rh_dcwcnt) /* Verify not too big */ + panic("rh11_iobuf: drive overran chan!"); + + rh->rh_wc = (rh->rh_wc + (wc << 1)) & MASK16; /* Add to 11-wd cnt */ + + if (!rhdc_ccwget(rh)) { /* Do mapping, set up our vars */ + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "failed]"); + return 0; /* Map error! */ + } + } + + /* See if word count ran out */ + if (rh->rh_wc == 0) { + /* Yep, RH11 has no real DC so all done now! */ + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "0]\r\n"); + return 0; /* Done, or no more */ + } + + /* Here, we still have a positive count, so return that. */ + wc = rh->rh_dcwcnt; /* Set up by ccwget */ + + /* Also return addr of buffer. RH11 has no DC so no skip/fill hackery */ + *avp = vm_physmap(rh->rh_dcbuf); + if (rh->rh_dcrev) /* If reverse xfer, negate count */ + wc = -wc; + + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "%d. (%lo)]\r\n", wc, (long)(rh->rh_dcbuf)); + + return wc; +} + + + +/* RH11_IOEND - Called by drive when I/O finished, terminate data channel +** I/O xfer. Assumes that rh11_iobuf has been called to update +** status after each transfer or sub-transfer, so channel word count +** is accurate reflection of data xfer. +** State may be one of: +** Error - either in drive, or from data channel. Assumption is +** that error has set all appropriate error bits (specifically +** Channel Error if channel status must be stored), and +** triggered PI as well. +** +** Drive stopped early due to no more DC buffer space. Indicated by +** non-zero returned block count. +** Drive stopped normally, block count 0. +** +** Checks to see whether to set Short/Long WC Error. +** Stores channel status if requested by PTCR bit RH11DO_SES. +** +** May initiate next RH11 data transfer. +*/ +static void +rh11_ioend(register struct device *drv, + int bc) /* Drive's final block count */ +{ + register struct rh11 *rh = (struct rh11 *)(drv->dv_ctlr); + + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "[rh11_ioend: %d.]\r\n", bc); + + + /* Update final IO xfer status */ + if (bc || rh->rh_dcwcnt) { + /* Drive still has stuff it wanted to do. + ** Set channel Short WC if WC==0, or Long WC if WC != 0, + ** and RH Chan Err. + */ +#if 0 /* Apparently for RH11 nothing happens in either case? */ + rh->rh_dcsts |= (rh->rh_dcwcnt ? CSW_LWCE : CSW_SWCE); + rh->rh_cond |= RH11CI_CE; +#endif + } + + /* Now say command done, whatever happened. */ + rh->rh_cs1 |= RH_XRDY; /* Done, channel ready */ + rh_pi(rh); /* Attempt triggering PI */ +} + +/* RH11 "Data Channel" routines. +** +*/ + +/* For completeness? Nothing calls this now */ +static void +rhdc_clear(register struct rh11 *rh) +{ + rh->rh_dcwcnt = 0; + rh->rh_dcbuf = 0; +} + +/* Set up vars for next drive-to-memory transfer +*/ +static int +rhdc_ccwget(register struct rh11 *rh) +{ + register int wc, wc2; + register paddr_t mem; + register h10_t map; /* Entry from UBA paging RAM */ + register unsigned pagno, pagoff; + struct ubctl *ub = rh->rh_dv.dv_uba; + + /* If word count reached zero, stop. */ + if ((wc = rh->rh_wc) == 0) { + rh->rh_dcwcnt = 0; + return 1; + } + + /* Find remaining word count in PDP10 words (rounds down) */ + wc = (-(wc | ~MASK16))>>1; /* Find # of PDP10 words */ + + /* Check unibus map to find starting phys address and how many words + we can handle contiguously in this map before a remap is needed + */ + + /* Determine memory address for sector */ + mem = rh->rh_ba; /* Bus address */ + mem |= (paddr_t) /* Add extended bits */ + (rh->rh_cs1 & (RH_XA17|RH_XA16)) << 8; + if (mem & ~(paddr_t)0377774) { /* High bit or low 2 are no-nos */ + rh->rh_cs1 |= RH_XMCP; /* Pretend bus error */ + /* (maybe shd use a CS2 bit?) */ + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "[rhdc_ccwget: bad bus addr %lo]", (long)mem); + return 0; + } + mem >>= 2; /* Get PDP10-word address */ + + /* High 6 bits (bit 17 known clear) are index into UB map */ + pagno = (mem>>9); + pagoff = mem & 0777; + map = ub->ubpmap[pagno]; + if (!(map & UBA_QVAL)) { /* If map entry not valid, */ + rh->rh_cs1 |= RH_XMCP; /* Pretend bus error (maybe shd use CS2?) */ + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "[rhdc_ccwget: no UB map for page %lo]", + (long)pagno); + return 0; + } + mem = ((paddr_t)(map & UBA_QPAG) << 9) | pagoff; /* Find phys addr */ + + /* Determine whether transfer needs to be limited because it crosses + ** non-contiguous physical pages (owing to unibus map) + */ + + for (wc2 = wc; ; ) { + + /* Find first DEC page boundary */ + if ((pagoff + wc2) <= 01000) + break; /* No boundary crossed */ + + /* Check next UBA page map entry for existence & validity */ + if (++pagno >= UBA_UBALEN || (!(ub->ubpmap[pagno] & UBA_QVAL))) { + rh->rh_cs1 |= RH_XMCP; /* Pretend bus error */ + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "[rhdc_ccwget: no UB map for page %lo]", + (long)pagno); + return 0; + } + if ((ub->ubpmap[pagno] & UBA_QPAG) + != 1+(ub->ubpmap[pagno-1] & UBA_QPAG)) { + /* Not contiguous phys page, must limit this xfer. */ + wc2 -= (01000 - pagoff); /* Finish rest of current page */ + wc -= wc2; /* Limit xfer */ + break; + } + wc2 -= 01000; + } + + rh->rh_dcwcnt = wc; /* Set up word count */ + rh->rh_dcbuf = mem; /* Set up phys PDP10 address */ + return 1; +} + +#endif /* KLH10_DEV_RH11 */ + diff --git a/src/dvrh11.h b/src/dvrh11.h new file mode 100644 index 0000000..560384f --- /dev/null +++ b/src/dvrh11.h @@ -0,0 +1,132 @@ +/* DVRH11.H - RH11 Controller definitions +*/ +/* $Id: dvrh11.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 1997, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dvrh11.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ +/* +** Portions of this file were derived from AI:SYSTEM;RH11 DEFS48 +*/ + +#ifndef RH11_INCLUDED +#define RH11_INCLUDED 1 + +#ifdef RCSID + RCSID(dvrh11_h,"$Id: dvrh11.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +#ifndef RH11_NSUP +# define RH11_NSUP 3 +#endif + +extern struct device *dvrh11_create(FILE *f, char *s); /* Initialization entry point */ + +/* RH11 Unibus register definitions: +*/ + +#if 0 +/* Default RH11 addresses and interrupt vectors */ +#define UBA1_RH_ADDR 0776700 /* UBA #1 RH11 registers start here */ +#define UBA1_RH_BR 6 /* Interrupts occur on level 6 (high pri) */ +#define UBA1_RH_VEC 0254 /* with this vector */ + +#define UBA3_RH_ADDR 0772440 /* UBA #3 RH11 registers start here */ +#define UBA3_RH_BR 6 /* Interrupts occur on level 6 (high pri) */ +#define UBA3_RH_VEC 0224 /* with this vector */ +#endif + +/* RH11 register definitions. Note that the ordering of these is different +** from that of the RH20. +** +** The CS1 register is shared between the RH11 and the currently selected +** drive. All others are either completely in the RH11 or completely in +** each drive. (this info per KSREF.MEM 12/78 p.3-6) +** +** RH11-only registers: +** CS1 bits 15-13, 10-6 inclusive (0163700) +** WC, BA, CS2, DB +** Each-Drive registers: +** CS1 bits 12-11, 5-0 inclusive (0014077) +** DA, DS, ER1, AS, LA, MR, DT, SN, OF, DC, CC, ER2, ER3, EC1, EC2 +** (Note AS is actually a summary of all drives) +*/ + +#define RH11R_CS1 0 /* (RH/DR) CTRL AND STATUS 1. */ +#define RH11R_WC 1 /* (RH) WORD COUNT. */ +#define RH11R_BA 2 /* (RH) UNIBUS ADDRESS. */ +#define RH11R_ADR 3 /* (DR) DESIRED ADDRESS. */ +#define RH11R_CS2 4 /* (RH) CTRL AND STATUS 2. */ +#define RH11R_STS 5 /* (DR) DRIVE STATUS. */ +#define RH11R_ER1 6 /* (DR) ERROR 1. */ +#define RH11R_ATN 7 /* (DR*) ATTENTION SUMMARY. */ +#define RH11R_LAH 010 /* (DR) LOOK AHEAD. */ +#define RH11R_BUF 011 /* (DR) DATA BUFFER. */ +#define RH11R_MNT 012 /* (DR) MAINTENANCE. */ +#define RH11R_TYP 013 /* (DR) DRIVE TYPE. */ +#define RH11R_SER 014 /* (DR) SERIAL NUMBER. */ +#define RH11R_OFS 015 /* (DR) OFFSET. */ +#define RH11R_CYL 016 /* (DR) DESIRED CYLINDER. */ +#define RH11R_CCY 017 /* (DR) CURRENT CYLINDER (RM03/80 unused) */ +#define RH11R_ER2 020 /* (DR) ERROR 2 (RM03/80 unused) */ +#define RH11R_ER3 021 /* (DR) ERROR 3 */ +#define RH11R_POS 022 /* (DR) ECC POSITION. */ +#define RH11R_PAT 023 /* (DR) ECC PATTERN. */ +#define RH11R_N 024 /* # of RH11 registers */ + + +/* RH-internal register bits */ + +#define RH_CS1_RH11 0163700 /* RH11 bits in CS1 */ +#define RH_CS1_DRIVE 014077 /* DR bits in CS1: Bit12+RH_XDVA+RH_XCMD */ + +/* CS1: (RH/DR) CTRL AND STATUS 1 */ +/* Apparently only RH_XTRE and RH_XMCP cause errors */ +/* SC is only set if some ATTN bits are on */ +#define RH_XSC (1<<15) /* [RH] RO -I Special Condition */ +#define RH_XTRE (1<<14) /* [RH] R/W 2 Transfer Error (set it to clear?) */ +#define RH_XMCP (1<<13) /* [RH] RO 2 Control Bus Parity Error */ + /* (1<<12) * [dr] ??? */ +#define RH_XDVA (1<<11) /* [dr] RO - Drive Available */ +#define RH_XPSE (1<<10) /* [RH] ? - Port Select */ +#define RH_XA17 (1<<9) /* [RH] R/W - UB Address Extension Bit 17 */ +#define RH_XA16 (1<<8) /* [RH] R/W - UB Address Extension Bit 16 */ +#define RH_XRDY (1<<7) /* [RH] RO 2 Ready (for next command) */ +#define RH_XIE (1<<6) /* [RH] R/W 2 Interrupt Enable */ +#define RH_XCMD 077 /* [dr] R/W 2 Bits 1-5 specify commands. */ +#define RH_XGO (1<<0) /* [dr] R/W 2 GO bit */ + +/* CS2: (RH) CTRL AND STATUS 2 */ +/* [i2] - Used by ITS,T20 */ +#define RH_YDLT (1<<15) /* RO 2 Data Late == CSW_OVN Overrun */ +#define RH_YWCE (1<<14) /* RO - Write Check Error */ +#define RH_YPE (1<<13) /* RO 2 Parity Error == CSW_MPAR */ +#define RH_YNED (1<<12) /* RO 2 Non-existant Drive */ +#define RH_YNEM (1<<11) /* RO 2 BA is NXM during DMA == CSW_NXM */ +#define RH_YPGE (1<<10) /* RO 2 Program Error == CSW_RH20E RH error */ +#define RH_YMXF (1<<9) /* RO 2 Missed Transfer == CSW_RH20E RH error */ +#define RH_YMDP (1<<8) /* RO 2 Mass Data Bus Parity Error == CSW_MPAR */ +#define RH_YOR (1<<7) /* ? - Output Ready (for Silo buffer diag.) */ +#define RH_YIR (1<<6) /* ? - Input Ready (for Silo buffer diag.) */ +#define RH_YCLR (1<<5) /* R/W 2 Controller Clear */ +#define RH_YPAT (1<<4) /* ? - Parity Test */ +#define RH_YBAI (1<<3) /* ? - Unibus Address Increment Inhibit */ +#define RH_YDSK 07 /* R/W 2 Unit Select */ + +#endif /* ifndef RH11_INCLUDED */ diff --git a/src/dvrh20.c b/src/dvrh20.c new file mode 100644 index 0000000..eba28e6 --- /dev/null +++ b/src/dvrh20.c @@ -0,0 +1,1149 @@ +/* DVRH20.C - Emulates RH20 disk devices for KL10 +*/ +/* $Id: dvrh20.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dvrh20.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#include "klh10.h" + +#if !KLH10_DEV_RH20 && CENV_SYS_DECOSF + /* Stupid gubbish needed to prevent OSF/1 AXP compiler from + ** halting merely because compiled file is empty! + */ +static int decosfcclossage; +#endif + +#if KLH10_DEV_RH20 /* Moby conditional for entire file */ + +#include /* For size_t etc */ +#include +#include + +#include "kn10def.h" /* This includes OSD defs */ +#include "kn10dev.h" +#include "dvuba.h" +#include "dvrh20.h" + +#ifdef RCSID + RCSID(dvrh20_c,"$Id: dvrh20.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +struct rh20 { + struct device rh_dv; /* Generic 10 device structure */ + + /* RH20-specific vars */ + int rh_no; /* RH20 Unit number (0-7) */ + uint18 rh_cond; /* CONI bits */ + int rh_pilev; /* PI level mask (0 if PIA=0) */ + + /* "Prep reg" settings */ + uint18 rh_prep; /* Prep reg LH */ + int rh_rs; /* Register select */ + int rh_ds; /* Drive select */ + struct device *rh_dsptr; + + uint32 rh_reg[8]; /* RH20 Internal registers */ + int rh_sbarf; /* TRUE if SBAR was set */ + int rh_bcnt; /* Current block count from PTCR */ + + /* Channel vars */ + int rh_eptoff; /* Offset of channel area in EPT */ + paddr_t rh_clp; /* DC "PC" - Current Command List Pointer */ + uint18 rh_dcsts; /* DC status flag bits (LH) */ + w10_t rh_dcccw; /* DC current cmd word */ + int rh_dcwcnt; /* DC cmd word count */ + paddr_t rh_dcbuf; /* DC cmd data buffer pointer */ + int rh_dchlt; /* TRUE if halt when current wordcount done */ + int rh_dcrev; /* TRUE if doing reverse data xfer */ + int rh_dcwrt; /* TRUE if doing device write */ + + + /* Drive bindings */ + struct device *rh_drive[8]; + int rh_attn; /* ATTN summary for all drives */ +}; + +/* Macro for simpler access to RH20 internal regs */ +#define RH20REG(c,r) ((c)->rh_reg[(r)-RH20_1ST]) + + +static int nrh20s = 0; +/* static */ struct rh20 dvrh20[RH20_NSUP]; + +#define RHDEBUG(rh) ((rh)->rh_dv.dv_debug) +#define RHDBF(rh) ((rh)->rh_dv.dv_dbf) + + +/* Function predecls */ + +/* Functions provided to device vector */ + +static w10_t rh20_pifnwd(struct device *d); +static void rh20_cono(struct device *d, h10_t erh); +static w10_t rh20_coni(struct device *d); +static void rh20_datao(struct device *d, w10_t w); +static w10_t rh20_datai(struct device *d); +static int rh20_bind(struct device *d, FILE *of, struct device *u, int num); +static int rh20_init(struct device *d, FILE *of); +static void rh20_reset(struct device *d); +#if 0 +static int rh20_readin(); /* Readin boot, if supported */ +#endif + +/* Functions provided to slave device */ + +static void rh20_attn(struct device *drv, int on); +static void rh20_drerr(struct device *drv); +static int rh20_iobeg(struct device *drv, int wflg); +static int rh20_iobuf(struct device *drv, register int wc, vmptr_t *avp); +static void rh20_ioend(struct device *drv, int bc); + +/* Completely internal functions */ + +static void rh_clear(struct rh20 *rh); +static void rh_picheck(struct rh20 *rh); +static void rh_pi(struct rh20 *rh); +static void rh_xfrbeg(struct rh20 *rh); +static void rh_clrattn(struct rh20 *rh, int msk); + +static void rhdc_init(struct rh20 *rh, int mbc); +static void rhdc_clear(struct rh20 *rh); +static int rhdc_ccwget(struct rh20 *rh); + +/* RH20 interface routines to KLH10 */ + +struct device * +dvrh20_create(FILE *f, char *s) +{ + register struct rh20 *rh; + + /* Parse string to determine which device to use, config, etc etc + ** But for now, just allocate sequentially. Hack. + */ + if (nrh20s >= RH20_NSUP) { + fprintf(f, "Too many RH20s, max: %d\n", RH20_NSUP); + return NULL; + } + rh = &dvrh20[nrh20s++]; /* Pick unused RH20 */ + + /* Initialize generic device part of RH20 struct */ + iodv_setnull(&rh->rh_dv); /* Initialize as null device */ + + rh->rh_dv.dv_dflags = DVFL_CTLR; + rh->rh_dv.dv_pifnwd = rh20_pifnwd; + rh->rh_dv.dv_cono = rh20_cono; + rh->rh_dv.dv_coni = rh20_coni; + rh->rh_dv.dv_datao = rh20_datao; + rh->rh_dv.dv_datai = rh20_datai; + + rh->rh_dv.dv_bind = rh20_bind; /* Controller, so can bind. */ + rh->rh_dv.dv_init = rh20_init; /* Set up own post-bind init */ + rh->rh_dv.dv_reset = rh20_reset; /* System reset (clear stuff) */ + + return &rh->rh_dv; +} + +static int +rh20_bind(struct device *d, /* Our device (controller) */ + FILE *of, + register struct device *slv, /* Slave device */ + int num) +{ + register struct rh20 *rh = (struct rh20 *)d; + + if (0 <= num && num < 8) ; + else { + if (of) fprintf(of, "RH20 can only bind up to 8 drives (#%d invalid).\n", num); + return FALSE; + } + + /* Backpointer and device # (dv_ctlr, dv_num) are already set up. */ + slv->dv_attn = rh20_attn; /* Set attention handler */ + slv->dv_drerr = rh20_drerr; /* Set exception handler */ + slv->dv_iobeg = rh20_iobeg; /* IO xfer beg */ + slv->dv_iobuf = rh20_iobuf; /* IO xfer buffer setup */ + slv->dv_ioend = rh20_ioend; /* IO xfer end */ + + rh->rh_drive[num] = slv; + + return TRUE; +} + +static int +rh20_init(register struct device *d, FILE *of) +{ + register struct rh20 *rh = (struct rh20 *)d; + + /* Transform device # into RH20 unit # */ + if (DEVRH20(0) <= rh->rh_dv.dv_num && rh->rh_dv.dv_num < DEVRH20(8)) { + rh->rh_no = (rh->rh_dv.dv_num - DEVRH20(0)) >> 2; + } else { + if (of) fprintf(of, "Trying to init RH20 with non-Massbus device #\n"); + return FALSE; + } + rhdc_init(rh, rh->rh_no); /* Initialize data channel for this MBC */ + + rh->rh_cond = 0; /* Clear all CONI bits */ + rh->rh_prep = 0; /* Clear prep reg */ + rh->rh_rs = rh->rh_ds = 0; /* Default selections 0 */ + rh->rh_dsptr = NULL; /* No drive */ + memset((char *)&(rh->rh_reg[0]), 0, sizeof(rh->rh_reg)); + rh->rh_sbarf = 0; + rh->rh_attn = 0; /* Clear ATTN summary */ + + return TRUE; +} + +static void +rh20_reset(struct device *d) +{ + rh_clear((struct rh20 *)d); +} + +/* PI: Get function word */ +static w10_t +rh20_pifnwd(struct device *d) +{ + register w10_t w; + +#if 0 + /* Clear PI request? + ** This is probably not correct; I suspect PI stays on until condition + ** is explicitly turned off with CONO, etc. + */ + if (rh->rh_dv.dv_pireq) /* If PI request outstanding, */ + (*rh->rh_dv.dv_pifun)(rh, 0); /* clear it. */ +#endif + + /* Return PI function word; only low 9 bits of addr are used. */ + LRHSET(w, PIFN_ASPEPT | PIFN_FVEC, + RH20REG((struct rh20 *)d, RH20_IVIR) & H10MASK); + return w; +} + +/* CONO 18-bit conds out +** Args D, ERH +** Returns nothing +*/ +static insdef_cono(rh20_cono) +{ + register struct rh20 *rh = (struct rh20 *)d; + register uint18 cond = erh; + + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "[rh20_cono: %lo]\r\n", (long)erh); + + + /* B24 - Clears RAE and associated PI req */ + if (cond & RH20CO_CRAE) { + rh->rh_cond &= ~RH20CI_RAE; + } + + /* B25 - Clear Massbus Controller. Resets RH20 and all drives to + ** power-up state. Will hang channel if xfer in progress, + ** unless RCLP also set. + */ + if (cond & RH20CO_CMC) { + rh_clear(rh); + } + + /* B26 - Clear all xfer error indicators (CONI 18-23, 26) */ + if (cond & RH20CO_TEC) { + rh->rh_cond &= ~( + RH20CI_DPE /* B18 - Data Parity Error */ + | RH20CI_DEE /* B19 - Drive Exception (xfer error) */ + | RH20CI_LWCE /* B20 - Long Word Count Error */ + | RH20CI_SWCE /* B21 - Short Word Count Error */ + | RH20CI_CE /* B22 - Channel Error */ + | RH20CI_DR /* B23 - Drive Response Error (ie no-ex) */ + | RH20CI_DOE /* B26 - Data Overrun */ + ); + } + + /* B27 - MassBus Enable + ** Note this doesn't actually do anything - code always assumes it's + ** enabled, to avoid time-wasting checks. + */ + if (cond & RH20CO_MBE) { + rh->rh_cond |= RH20CI_MBE; /* Same bit, could just mask in */ + } + + /* B28 - Reset Cmd List Ptr (inits channel) */ + if (cond & RH20CO_RCLP) { + rhdc_clear(rh); + } + + /* B29 - Clears cmd file xfer logic. + ** (Used if previous data xfer caused xfer error, CONI bits 18-23, 26). + */ + if (cond & RH20CO_DSCRF) { + rh->rh_cond &= ~(RH20CI_SCRF | RH20CI_PCRF); /* No regs loaded */ + rh->rh_sbarf = 0; /* No SBAR either */ + } + + /* B30 - Allow Massbus ATTN to generate PI. + ** Unlike most of the other CONO bits, the state of this one is + ** always significant; MBAE is turned off if not set. + */ + if (cond & RH20CO_MEAE) { + rh->rh_cond |= RH20CI_MBAE; /* B30 - Enable PI on RH20CI_MBA */ + } else + rh->rh_cond &= ~RH20CI_MBAE; /* B30 - Enable PI on RH20CI_MBA */ + + /* B31 - Stop Transfer. (nothing cleared) */ + if (cond & RH20CO_ST) { + /* Ugh, nothing we can really do about this. */ + } + + /* B32 - Clear Cmd Done, clears CONI bit 32 */ + if (cond & RH20CO_CCMD) { +#if 1 /* Avoid T20 ILLGO BUGHLTs for stacked xfers! */ + /* Start xfer if turning off a live CMD bit and STCR has a pending + ** xfer command; assume T20 int handler has fired. + */ + if ((rh->rh_cond & (RH20CI_CMD|RH20CI_PCRF|RH20CI_SCRF)) + == (RH20CI_CMD|RH20CI_SCRF)) { + /* Both CMD and SCRF are on, and PCRF is off -- start new xfer! */ + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "[rh20_cono 2nd xfrbeg!]"); + rh->rh_cond &= ~RH20CI_CMD; /* Clear it */ + rh_xfrbeg(rh); + } else +#endif + rh->rh_cond &= ~RH20CI_CMD; /* Clear it */ + } + + /* B33-35 - PI channel assignment (0 = none) */ + rh->rh_cond = (rh->rh_cond & ~RH20CI_PIA) + | (cond & RH20CO_PIA); + rh->rh_pilev = (1 << (7-(cond & RH20CO_PIA))) & 0177; /* 0 if PIA=0 */ + if (rh->rh_dv.dv_pireq && (rh->rh_dv.dv_pireq != rh->rh_pilev)) { + /* Changed PIA while PI outstanding; flush it, let picheck re-req */ + (*rh->rh_dv.dv_pifun)(&rh->rh_dv, 0); /* clear it. */ + } + + /* Check for any changes to PI status */ + rh_picheck(rh); +} + +/* CONI 36-bit conds in +** Args D +** Returns condition word +*/ +static insdef_coni(rh20_coni) +{ + register w10_t w; + + if (RHDEBUG((struct rh20 *)d)) + fprintf(RHDBF((struct rh20 *)d), "[rh20_coni: %lo]", + (long)((struct rh20 *)d)->rh_cond); + + LRHSET(w, 0, ((struct rh20 *)d)->rh_cond); + return w; +} + +/* DATAO word out +** Args D, W +** Returns nothing +*/ +static insdef_datao(rh20_datao) +{ + register struct rh20 *rh = (struct rh20 *)d; + + /* First make sure it's OK to do anything at all, by checking RAE. + ** Note that the setting of DRAES in the current DATAO has no effect on + ** the initial test -- although it does affect the handling of RAE if + ** it is raised during the current DATAO. + */ + if ((rh->rh_cond & RH20CI_RAE) && !(rh->rh_prep & RH20DO_DRAES)) { + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "[rh20_datao: %o.? %lo,,%lo RAE-ignored]\r\n", + rh->rh_no, (long)LHGET(w), (long)RHGET(w)); + + return; /* Ugh, RAE but no DRAES, so do nothing */ + } + + /* Now always set prep reg values. + ** Note that CBEP is ignored. + */ + rh->rh_prep = LHGET(w) & + (RH20DO_RS | RH20DO_LR | RH20DO_DRAES | RH20DO_DS); + rh->rh_rs = (rh->rh_prep >> 12) & 077; + rh->rh_ds = rh->rh_prep & RH20DO_DS; + rh->rh_dsptr = rh->rh_drive[rh->rh_ds]; + + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "[rh20_datao: %o.%o RS%o <= %lo,,%lo]\r\n", + rh->rh_no, rh->rh_ds, rh->rh_rs, + (long)LHGET(w), (long)RHGET(w)); + + /* Now see whether to actually load anything else */ + if (!(rh->rh_prep & RH20DO_LR)) + return; /* Nope, just prep reg, we're done. */ + + switch (rh->rh_rs) { + default: + if (rh->rh_rs <= 037) { + /* External register. + ** In the real RH20, external registers cannot be updated if + ** a command file transfer (using PBAR&PTCR) is in progress; + ** the RH20 waits until the massbus is free. + ** This may become a consideration for future asynch operation. + ** Hopefully no monitor tries to do this. + */ + /* Note RHR_ATTN needs special handling, just as for DATAI. + */ + if (rh->rh_rs == RHR_ATTN) { /* Attn summary? */ + /* Turn off ATTN bit for all drive bits provided. + */ + rh_clrattn(rh, (int)RHGET(w)&MASK16); + return; + } + if (rh->rh_dsptr && + (*rh->rh_dsptr->dv_wrreg)(rh->rh_dsptr, + rh->rh_rs, ((int)RHGET(w))&MASK16)) + return; /* External reg write succeeded */ + + if (RHDEBUG(rh)) { + fprintf(RHDBF(rh), "[rh20_datao: %o.%o RAE %o", + rh->rh_no, rh->rh_ds, rh->rh_rs); + if (!rh->rh_dsptr) + fprintf(RHDBF(rh), " (no drive)]"); + else + fprintf(RHDBF(rh), "]"); + } + + /* External reg write failed, set RAE and maybe do PI. */ + rh->rh_cond |= RH20CI_RAE; /* Set RAE */ + if (!(rh->rh_prep & RH20DO_DRAES)) { + rh_pi(rh); /* Trigger PI */ + } + } + return; /* Unspecified RH20 reg (040-067), just do nothing */ + + /* Internal RH20 registers! */ + + /* Big hairy case -- this is what ultimately starts a data xfer!! */ + case RH20_STCR: /* R/W Secondary Transfer Control Reg */ + RH20REG(rh, RH20_STCR) = W10_U32(w); /* First load STCR */ + if (rh->rh_cond & RH20CI_PCRF) { /* Primary regs loaded? */ + rh->rh_cond |= RH20CI_SCRF; /* Yep, say STCR loaded */ + return; + } +#if 1 /* Avoid T20 ILLGO BUGHLTs for stacked xfers! */ + /* Don't start xfer if CMD bit still set -- means T20 hasn't yet + ** responded to the xfer-done interrupt! + */ + if (rh->rh_cond & RH20CI_CMD) { + if (RHDEBUG(rh)) { + fprintf(RHDBF(rh), "[rh20_datao postponed 2nd xfer!]"); + } + rh->rh_cond |= RH20CI_SCRF; /* Yep, say STCR loaded */ + return; + } +#endif + rh_xfrbeg(rh); /* Nothing in PTCR, start xfer! */ + return; + + case RH20_SBAR: /* R/W Secondary Block Address Reg */ + rh->rh_sbarf = TRUE; /* Say SBAR loaded */ + break; /* Then go load it */ + + /* Simple can't-write cases */ + case RH20_PBAR: /* RO Primary Block Address Reg */ + case RH20_PTCR: /* RO Primary Transfer Control Reg */ + case RH20_RR: /* RO Read Reg (Diagnostic only) */ + return; /* Can't write, so no-op these */ + + /* Simple write-register cases */ + case RH20_IVIR: /* R/W Interrupt Vector Index Reg */ + case RH20_WR: /* WO Write Reg (Diagnostic only) */ + case RH20_DCR: /* WO Diagnostic Control Reg (Diags only) */ + break; /* Just do simple write */ + } + + /* Do a simple internal register write */ + RH20REG(rh, rh->rh_rs) = W10_U32(w); /* Simple write */ +} + +/* DATAI word in +** Args D +** Returns data word +*/ +static insdef_datai(rh20_datai) +{ + register struct rh20 *rh = (struct rh20 *)d; + register w10_t w; + + /* For the RH20 the register & unit selected is specified by the + ** preparation register, set by a previous DATAO. + */ + if (rh->rh_rs >= RH20_1ST) { + /* Internal register, simple and always succeeds */ + LRHSET(w, (RH20REG(rh, rh->rh_rs)>>H10BITS)&H10MASK, + (RH20REG(rh, rh->rh_rs)&H10MASK)); + + } else if (rh->rh_rs <= 037) { + /* External register. + ** Note RHR_ATTN needs to be handled specially, as it must collect + ** attention status of all drives! + */ + register uint32 erdata; + + if (rh->rh_rs == RHR_ATTN) { + LRHSET(w, RH20DI_TRA, rh->rh_attn); /* Special summary */ + + } else if (rh->rh_dsptr && (MASK16 >= (erdata = + (*rh->rh_dsptr->dv_rdreg)(rh->rh_dsptr, rh->rh_rs)))) { + LRHSET(w, RH20DI_TRA, erdata); + + } else { + /* Non-existent drive or register access error. + ** Set the RAE CONI bit and possibly interrupt. + */ + if (RHDEBUG(rh)) { + fprintf(RHDBF(rh), "[rh20_datai: %o.%o RAE %o: ", + rh->rh_no, rh->rh_ds, rh->rh_rs); + if (!rh->rh_dsptr) + fprintf(RHDBF(rh), "no drive]"); + else + fprintf(RHDBF(rh), "%lo]", (long)erdata); + } + LRHSET(w, RH20DI_CBPE, 0); + rh->rh_cond |= RH20CI_RAE; /* Set RAE */ + if (!(rh->rh_prep & RH20DO_DRAES)) { + rh_pi(rh); /* Trigger PI */ + } + } + + } else { + /* Unspecified RH20 register (040-067) just does nothing */ + LRHSET(w, 0, 0); + } + + /* Always stuff in RS and LR fields from prep reg */ + LHSET(w, (LHGET(w) & ~(RH20DO_RS|RH20DO_LR)) + | ((RH20DO_RS|RH20DO_LR) & rh->rh_prep)); + + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "[rh20_datai: %o.%o RS%o => %lo,,%lo]\r\n", + rh->rh_no, rh->rh_ds, rh->rh_rs, + (long)LHGET(w), (long)RHGET(w)); + return w; +} + +/* RH20 internal routines (internal registers etc) */ + +/* RH_CLEAR - Clear controller +** Also does equivalent of asserting Massbus INIT, by invoking +** the reset routine for all drives. +*/ +static void +rh_clear(register struct rh20 *rh) +{ + register int i; + + if (rh->rh_dv.dv_pireq) /* If PI request outstanding, */ + (*rh->rh_dv.dv_pifun)(&rh->rh_dv, 0); /* clear it. */ + + /* Stop and flush any in-progress xfer? */ + + /* Need to figure out just which bits get cleared. */ +#if 1 /* For now, all of them */ + rh->rh_cond = 0; /* Clear all CONI bits */ + rh->rh_prep = 0; /* Clear prep reg */ + rh->rh_rs = rh->rh_ds = 0; /* Default selections 0 */ + rh->rh_dsptr = NULL; /* No drive */ + memset((char *)&(rh->rh_reg[0]), 0, sizeof(rh->rh_reg)); + rh->rh_sbarf = 0; +#endif + + /* Clear all drives for this controller */ + for (i = 0; i < 8; ++i) + if (rh->rh_drive[i]) + (*(rh->rh_drive[i]->dv_reset))(rh->rh_drive[i]); +} + +/* RH_PICHECK - Check RH20 conditions to see if PI should be attempted. +*/ +static void +rh_picheck(register struct rh20 *rh) +{ + /* If any possible interrupt bits are set */ + if (rh->rh_cond & (RH20CI_RAE | RH20CI_MBA | RH20CI_CMD)) { + + if ((rh->rh_cond & RH20CI_CMD) /* CMD DONE always interrupts */ + || ((rh->rh_cond & RH20CI_MBA) && (rh->rh_cond & RH20CI_MBAE)) + || ((rh->rh_cond & RH20CI_RAE) && (!(rh->rh_prep & RH20DO_DRAES)))) { + rh_pi(rh); + return; + } + } + /* Here, shouldn't be requesting PI, so if our request bit is set, + ** turn it off. + */ + if (rh->rh_dv.dv_pireq) { /* If set while shouldn't be, */ + (*rh->rh_dv.dv_pifun)(&rh->rh_dv, 0); /* Clear it! */ + } +} + + +/* RH20_PI - trigger PI for selected RH20. +** This could perhaps be an inline macro, but for now +** having it as a function helps debug. +*/ +static void +rh_pi(register struct rh20 *rh) +{ + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "[rh20_pi: %o]", rh->rh_pilev); + + if (rh->rh_pilev /* If have non-zero PIA */ + && !(rh->rh_dv.dv_pireq)) { /* and not already asking for PI */ + (*rh->rh_dv.dv_pifun)(&rh->rh_dv, rh->rh_pilev); /* then do it! */ + } +} + +/* RH_CLRATTN - Clear ATTN bit for all masked drives. +** Forces call even if it seems unnecessary, just to be safe. +*/ +static void +rh_clrattn(register struct rh20 *rh, int msk) +{ + register int i; + register struct device *drv; + + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "[rh_clrattn: %o]", msk); + msk &= 0377; /* Allow only 8 bits for 8 drives */ + for (i = 0; msk; ++i, msk >>= 1) { + if ((msk & 01) && (drv = rh->rh_drive[i])) + (*drv->dv_wrreg)(drv, RHR_ATTN, 1); /* Tell drv to clr its ATTN */ + } +} + + +/* +NOTE: + This code does not fully emulate the RH20's secondary transfer +registers, for the following reasons: + + - Only TOPS-20 uses this feature (which it calls a "stacked" transfer), + and only for disk. TOPS-10 doesn't use it. + - The T20 code has a timing problem as follows: + +T20 is doing a so-called "stacked" transfer using the ability of the +RH20 to store a backup xfer command and start it when the primary xfer +is done. But then T20 sometimes gets confused -- when it checks the +logout area to see what the last CCW pointed to, it finds a page number +that, while correct (is one past the last phys xfer finished), is NOT what +T20 expects; T20 compares it against the result expected for the FIRST +transfer, not the second one, and promptly dies with an ILLGO BUGHLT. + +Somehow, the 2nd transfer is not only being initiated but also +completed before T20 expects it to be. + +The odd thing about all this is that this doesn't happen for the +synchronous case, where transfers complete immediately; you can't get +much more extreme than an "instantaneous" disk! Somehow, the fact +that the "done" interrupt is delayed is making things different. It's +not even a case of having the wrong "xfer in progress" bits set, +because T20 isn't even doing a CONI in between the two transfer loads +(verified by debug output). + +The theory so far is something like this: + + [A1] RH PI suspended. + [A2] T20 initiates 1st xfer. + [A3] RH PI allowed. + ... + [B1] RH PI suspended. + [B2] T20 initiates 2nd (stacked) xfer. + [B3] RH PI allowed. + + If T20 expects to receive a distinct PI int for each xfer, then + this sequence will lose if: + - Xfer A completes after [B1] + - Xfer B completes before [B3] + Which is what appears to be happening when the asynch disk + hits ILLGO. That is, xfer A completes in the middle of B2, and + xfer B is started as soon as the appropriate B2 DATAO is given, + but although the RH20 makes a PI request, no interrupt is taken + until after xfer B is done as well! + +If this is correct, this would explain why synch disk wins, cuz the +pending PI would happen in the window between A and B, and T20 is +happy. This would also explain why fast asynch loses, cuz xfer B +happens much faster than T20 expects. + +The solution currently adopted here is for the RH20 to defer the 2nd +xfer until the OS explicitly acknowledges the first xfer by turning +off the DONE bit in the RH20 CONI word. + +This is not what a real RH20 does, but a real RH20 is guaranteed of some +non-trivial length of time for a transfer operation to complete. An +alternative solution, which only makes sense if the following conditions +hold: + + 1) T20 + 2) disk performance bottleneck + 3) using raw partition + 4) frequent stacked xfers + THEN: + Could try a sort of read-ahead or write-ahead hack where the +RP subproc carried out both xfers, so that the data actually gets +transferred at top speed, but the RH side hides that fact from the 10 +and the 2nd transfer appears not to be done until CMD-DONE is turned +off for the 1st transfer; then for the 2nd xfer the RH doesn't actually +do I/O but instead just rigs the channel logout area and says 2nd +transfer is done. + But for now, the wait-for-CMD-clear solution should work. + +*/ + +/* RH_XFRBEG - Called to start a data transfer. +** Assumes no xfer in progress, and STCR has stuff; +** takes whatever is there, plus (optionally) +** whatever is in SBAR, and starts the xfer going. +*/ +static void +rh_xfrbeg(register struct rh20 *rh) +{ + register struct device *drv; + register uint32 bar, tcr; + register int barf; + + rh->rh_cond &= ~RH20CI_SCRF; /* Clear SCR Full flag */ + rh->rh_cond |= RH20CI_PCRF; /* Set PCR Full flag */ + barf = rh->rh_sbarf; + bar = RH20REG(rh, RH20_PBAR) = RH20REG(rh, RH20_SBAR); + tcr = RH20REG(rh, RH20_PTCR) = RH20REG(rh, RH20_STCR); + + if (RHDEBUG(rh)) { + fprintf(RHDBF(rh), "[rh_xfrbeg: TCR %lo", (long)tcr); + if (rh->rh_sbarf) fprintf(RHDBF(rh), " BAR %lo", (long)bar); + fprintf(RHDBF(rh), "]\r\n"); + } + + /* What to do about old values of secondary xfer regs? + ** I doubt the real hardware clears them, but to help debugging, + ** this seems reasonable. + */ + RH20REG(rh, RH20_SBAR) = 0; + RH20REG(rh, RH20_STCR) = 0; + rh->rh_sbarf = 0; /* This *must* be cleared */ + + if (tcr & (RH20DO_RCLP<rh_drive[(tcr >> H10BITS) & RH20DO_DS])) { + /* Ugh, non-existent drive! Set CONI DR bit and trigger PI. */ + rh->rh_cond |= RH20CI_DR|RH20CI_CMD; /* Drive Response Error */ + rh_pi(rh); /* Trigger PI */ + return; + } + + /* If SBAR was set, then give that to drive first. + ** It appears that the drive select field in the BAR reg is ignored; + ** the actual drive is whatever the TCR specifies! + */ + if (barf) { +#if 0 /* This was going off constantly */ + /* Do debug check - BAR and TCR better have same drive select! */ + if ((bar & RH20DO_DS) != (tcr & RH20DO_DS)) { + if (rh->rh_dv.dv_dbf) + fprintf(rh->rh_dv.dv_dbf, + "[rh_xfrbeg: drive select mismatch, bar=%lo, tcr=%lo]\r\n", + (long)bar, (long)tcr); + } +#endif + if (!(*drv->dv_wrreg)(drv, RHR_BAFC, (int)(bar & MASK16))) { + /* Ugh, drive complained about setting BAR??? */ + panic("rh_xfrbeg: BAR set failure"); + rh->rh_cond |= RH20CI_DR|RH20CI_CMD; /* Drive Resp Err */ + rh_pi(rh); /* Trigger PI */ + return; + } + } + + /* Set up positive block count. Note field is interpreted as negative, and + ** in the real RH20 the count is done when adding 1 causes overflow. + ** Thus if the field is all 0s it is interpreted as -2000, not as 0. + */ + rh->rh_bcnt = - (((tcr>>6)&(RH20DO_NBC>>6)) | ~(RH20DO_NBC>>6)); + + /* OK, ready to start xfer... */ + if (!(*drv->dv_wrreg)(drv, RHR_CSR, (int)(tcr & MASK16))) { + /* Ugh, drive complained about setting CSR??? */ + panic("rh_xfrbeg: CSR set failure"); + rh->rh_cond |= RH20CI_DR|RH20CI_CMD; /* Drive Resp Err */ + rh_pi(rh); /* Trigger PI */ + return; + } + + /* That's all, drive's code does the rest! */ +} + + +/* The following rh20_ functions are all called by the drive via +** the controller/drive vectors. +*/ + +/* RH20_ATTN - Called by drive to assert or clear its attention bit. +** Note arg is pointer to rh20drv struct. +*/ +static void +rh20_attn(register struct device *drv, int on) +{ + register struct rh20 *rh = (struct rh20 *)(drv->dv_ctlr); + register int rbit = (1 << drv->dv_num); /* Attention bit to use */ + + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "[rh20_attn: bit %o %s]", + rbit, on ? "on" : "off"); + + /* If turning ATN bit on, always interrupt if enabled, even if ATN + was already on. This is because it could have been previously + turned on while RH20CI_MBAE was off, and thus is not a reliable sign + of whether an interrupt is already pending. + */ + if (on) { + rh->rh_attn |= rbit; /* Add new attention bit! */ + rh->rh_cond |= RH20CI_MBA; /* Assert Massbus Attention */ + if (rh->rh_cond & RH20CI_MBAE) /* If OK for attn to int */ + rh_pi(rh); /* then try to trigger PI! */ + } else { + if (rh->rh_attn & rbit) { + rh->rh_attn &= ~rbit; /* Turn off attention bit */ + if (!rh->rh_attn /* If no longer have any ATTNs */ + && (rh->rh_cond & RH20CI_MBA)) { /* ensure MB attn is off */ + /* Changing MBA state... may have to clean up PI */ + rh->rh_cond &= ~RH20CI_MBA; /* ensure MB attn is off */ + if (rh->rh_dv.dv_pireq) /* If had PI request, */ + rh_picheck(rh); /* Sigh, must re-check stuff */ + } + } + } +} + + +/* RH20_DRERR - Called by drive to assert an exception error during an +** I/O transfer. +*/ +static void +rh20_drerr(register struct device *drv) +{ + register struct rh20 *rh = (struct rh20 *)(drv->dv_ctlr); + + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "[rh20_drerr]"); + + if (!(rh->rh_cond & RH20CI_DEE)) { + rh->rh_cond |= RH20CI_DEE; /* Assert Drive Exception Error */ + rh_pi(rh); /* Then try to trigger PI */ + } + + /* If I/O transfer in progress, abort it gracefully */ + if (1) + rh20_ioend(drv, 1); /* Assume always have a block left */ +} + + +/* RH20_IOBEG - Called by drive to set up data channel just prior to +** starting an I/O xfer. +** Returns 0 if failure; drive should stop immediately and not +** invoke rhdv_iobuf, but must still call rhdv_ioend. +** Else returns # of block units (sectors or records) to do I/O for. +*/ +static int +rh20_iobeg(struct device *drv, int wflg) +{ + register struct rh20 *rh = (struct rh20 *)(drv->dv_ctlr); + + rh->rh_dcsts = CSW_CLRSET; /* Clear channel status */ + rh->rh_dcwrt = wflg; /* Remember if writing */ + if (!rhdc_ccwget(rh)) { + rh->rh_cond |= RH20CI_CE /* Say Channel Error */ + | RH20CI_CMD; /* and Command Done */ + rh_pi(rh); /* Try to trigger PI */ + return 0; /* Tell drive we failed */ + } + + rh->rh_cond &= ~RH20CI_CNR; /* Channel active now! */ + return rh->rh_bcnt; +} + + +/* RH20_IOBUF - Called by drive to query data channel to find source +** or dest buffer for I/O xfer. Can be called repeatedly for one xfer. +** Caller must provide # of words used from the last call (0 if none). +** In particular, final transfer MUST invoke this to tell the channel +** how many words were actually used. +** +** If returns 0, no data or space left. +** If returns +, OK, *avp NULL if skipping, else vmptr to mem. +** If returns -, wants to do reverse transfer. +** Not clear whether buffer pointer points to LAST word of +** block to write, or ONE PAST the last word. Assuming +** the former, based on DEC DC doc (p. DATA/2-7) desc of address. +*/ +static int +rh20_iobuf(struct device *drv, + register int wc, /* Max 11 bits of word count, so int OK */ + vmptr_t *avp) +{ + register struct rh20 *rh = (struct rh20 *)(drv->dv_ctlr); + + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "[rh20_iobuf: %d. => ", wc); + + if (!rh->rh_dcwcnt) { /* Nothing left */ + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "0]\r\n"); + return 0; + } + + if (wc) { + /* If drive is updating our IO xfer status, do it. */ + + /* Update buffer pointer, assuming wc has correct direction sign */ + if (rh->rh_dcbuf) + rh->rh_dcbuf += wc; + + if (wc < 0) { /* Verify direction consistent */ + if (!rh->rh_dcrev) + panic("rh20_iobuf: Neg wc for fwd xfer!"); + wc = -wc; /* Make positive */ + } else if (rh->rh_dcrev) + panic("rh20_iobuf: Pos wc for rev xfer!"); + + if ((rh->rh_dcwcnt -= wc) < 0) /* Update remaining wd cnt */ + panic("rh20_iobuf: drive overran chan!"); + + /* See if word count ran out */ + if (rh->rh_dcwcnt == 0) { + /* Yep, was this the last cmd (halt or last xfer?) + ** If not, try to get another data xfer CCW. + */ + if (rh->rh_dchlt || !rhdc_ccwget(rh)) { + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "0]\r\n"); + return 0; /* Done, or no more */ + } + } + } + + /* Here, we still have a positive count, so return that. */ + wc = rh->rh_dcwcnt; + + /* Also return addr of buffer. But there are a few special cases + ** to check for, sigh... + */ + if (rh->rh_dcbuf) + *avp = vm_physmap(rh->rh_dcbuf); + else { /* Ugh! Special fill or skip hack. */ + if (rh->rh_dcwrt) { /* If writing, use fill wds from EPT */ + *avp = vm_physmap(cpu.mr_ebraddr + EPT_CB0); + if (wc > 4) + wc = 4; + } else { + *avp = NULL; /* Tell drive to skip input */ + } + } + if (rh->rh_dcrev) /* If reverse xfer, negate count */ + wc = -wc; + + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "%d. (%lo)]\r\n", wc, (long)(rh->rh_dcbuf)); + + return wc; +} + + + +/* RH20_IOEND - Called by drive when I/O finished, terminate data channel +** I/O xfer. Assumes that rh20_iobuf has been called to update +** status after each transfer or sub-transfer, so channel word count +** is accurate reflection of data xfer. +** State may be one of: +** Error - either in drive, or from data channel. Assumption is +** that error has set all appropriate error bits (specifically +** Channel Error if channel status must be stored), and +** triggered PI as well. +** +** Drive stopped early due to no more DC buffer space. Indicated by +** non-zero returned block count. +** Drive stopped normally, block count 0. +** +** Checks to see whether to set Short/Long WC Error. +** Stores channel status if requested by PTCR bit RH20DO_SES. +** +** May initiate next RH20 data transfer. +*/ +static void +rh20_ioend(register struct device *drv, + int bc) /* Drive's final block count */ +{ + register struct rh20 *rh = (struct rh20 *)(drv->dv_ctlr); + + if (RHDEBUG(rh)) + fprintf(RHDBF(rh), "[rh20_ioend: %d.]\r\n", bc); + + + /* Update final IO xfer status */ + if (bc || rh->rh_dcwcnt) { + /* Drive still has stuff it wanted to do. + ** Set channel Short WC if WC==0, or Long WC if WC != 0, + ** and RH20 Chan Err. + */ + rh->rh_dcsts |= (rh->rh_dcwcnt ? CSW_LWCE : CSW_SWCE); + rh->rh_cond |= RH20CI_CE; + } + + /* See whether to store channel status */ + if ((RH20REG(rh, RH20_PTCR) /* If wanted state stored */ + & (RH20DO_SES<rh_cond & RH20CI_CE)) { /* or any kind of chan error */ + register vmptr_t vp; + register w10_t w; + + vp = vm_physmap(cpu.mr_ebraddr + rh->rh_eptoff); + + /* Store status bits and current cmd list ptr */ + if (rh->rh_dcwcnt) + rh->rh_dcsts |= CSW_NOWC0; + LRHSET(w, rh->rh_dcsts | ((rh->rh_clp >> H10BITS)&CSW_HIADR), + rh->rh_clp & H10MASK); + vm_pset(vp+RH20_EPT_CST(0), w); + + /* Reconstruct current CCW and store it */ + LRHSET(w, (LHGET(rh->rh_dcccw)&CCW_OP) + | (rh->rh_dcwcnt<<4) | ((rh->rh_dcbuf>>H10BITS)&CCW_ADR), + rh->rh_dcbuf & H10MASK); + vm_pset(vp+RH20_EPT_CCW(0), w); + } + + /* Now say command done, whatever happened. */ + rh->rh_cond &= ~RH20CI_PCRF; /* Primary regs now free */ + rh->rh_cond |= RH20CI_CMD | RH20CI_CNR; /* Done, channel ready */ + rh_pi(rh); /* Attempt triggering PI */ + + /* Now if no errors, check for secondary TCR and initiate it? */ + /* NO -- see explanation at start of rh_xfrbeg!! */ +} + +/* RH20 Data Channel routines. +** +** init/reset - called by RH20 +** ccwget - called by RH20 when drive wants to start xfer or needs more +** data/buffer space from channel. +*/ +static void +rhdc_init(register struct rh20 *rh, + int mbc) /* Massbus controller # (0-7) */ +{ + rh->rh_eptoff = mbc * 4; /* Offset of EPT area */ + rhdc_clear(rh); +} + +static void +rhdc_clear(register struct rh20 *rh) +{ + /* Reset channel PC to point at 1st wd of channel area in EPT */ + rh->rh_clp = rh->rh_eptoff + cpu.mr_ebraddr; + + rh->rh_dcsts = CSW_CLRSET; /* Clear status */ + LRHSET(rh->rh_dcccw, 0, 0); + rh->rh_dcwcnt = 0; + rh->rh_dcbuf = 0; + rh->rh_dchlt = TRUE; +} + +static int +rhdc_ccwget(register struct rh20 *rh) +{ + register w10_t w; + register paddr_t pa; + register int wc; + + /* Grovel down cmd list until hit valid xfer cmd, use that to set up */ + for (;;) { + /* Fetch current cmd word */ + w = vm_pget(vm_physmap(rh->rh_clp)); /* Get cmd word */ + pa = W10_U32(w) & MASK22; + wc = (LHGET(w) & CCW_CNT) >> (22-18); + + switch (LHGET(w) & CCW_OP) { + case CCW_HLT: + /* Use addr as CLP for next call, and do stuff to halt. + ** Should this cause a "Short Word Count" channel error, or + ** something else since transfer was never started? + ** For now, pretend zero word count, fail as if short WC. + */ + rh->rh_clp = pa; /* Set new "PC" */ + rh->rh_dcbuf = pa; /* Also set here in case status stored */ + rh->rh_dcccw = w; /* Remember current cmd */ + rh->rh_dcwcnt = 0; + rh->rh_dchlt = TRUE; + rh->rh_dcrev = 0; + return 0; + + case CCW_JMP: + rh->rh_clp = pa; /* Set up CLP and loop to effect jump */ + continue; + + case CCW_XFR: /* Forward transfer, no halt */ + rh->rh_dchlt = 0; + rh->rh_dcrev = 0; + break; + + case CCW_XFR|CCW_XHLT: /* Forward transfer, halt */ + rh->rh_dchlt = TRUE; + rh->rh_dcrev = 0; + break; + + case CCW_XFR|CCW_XREV|CCW_XHLT: + rh->rh_dchlt = 0; + rh->rh_dcrev = TRUE; + break; + + case CCW_XFR|CCW_XREV: + rh->rh_dchlt = TRUE; + rh->rh_dcrev = TRUE; + break; + + default: + /* Perhaps assert Channel Error (RH20CI_CE) here? */ + panic("rhdc_ccwget: Bad CCW op %lo", (long)(LHGET(w) & CCW_OP)); + return 0; + } + + rh->rh_dcccw = w; /* Remember current cmd */ + rh->rh_dcwcnt = wc; /* Set up word count */ + rh->rh_dcbuf = pa; /* Set up address */ + rh->rh_clp = (rh->rh_clp + 1) & MASK22; /* Bump "PC" */ + return 1; + } +} + +#endif /* KLH10_DEV_RH20 */ + diff --git a/src/dvrh20.h b/src/dvrh20.h new file mode 100644 index 0000000..21d19df --- /dev/null +++ b/src/dvrh20.h @@ -0,0 +1,236 @@ +/* DVRH20.H - RH20 Massbus Controller definitions (generic) +*/ +/* $Id: dvrh20.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dvrh20.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#ifndef RH20_INCLUDED +#define RH20_INCLUDED 1 + +#ifdef RCSID + RCSID(dvrh20_h,"$Id: dvrh20.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +#ifndef RH20_NSUP +# define RH20_NSUP 8 +#endif + +extern struct device *dvrh20_create(FILE *, char *); + +/* RH20 Massbus Data Channel definitions +*/ + +/* Channel Status/Logout area in the EPT +*/ +#define RH20_EPT_CCL(n) (0+(n<<2)) /* Channel Command List */ +#define RH20_EPT_CST(n) (1+(n<<2)) /* Channel Status & Pointer */ +#define RH20_EPT_CCW(n) (2+(n<<2)) /* Current Channel Cmd Word */ +#define RH20_EPT_CIV(n) (3+(n<<2)) /* Unused by hardware; software + ** uses for PI vectoring + */ +/* Format of Channel Command Word +*/ +#define CCW_OP 0700000 /* LH: Channel Command Opcode */ +# define CCW_HLT 0000000 /* LH: HALT opcode */ +# define CCW_JMP 0200000 /* LH: JUMP opcode */ +# define CCW_XFR 0400000 /* LH: XFER opcode, plus next 2 bits: */ +# define CCW_XHLT 0200000 /* LH: Xfer cmd: "Halt after xfer" */ +# define CCW_XREV 0100000 /* LH: Xfer cmd: "Reverse xfer" */ +#define CCW_CNT 077760 /* LH: Word Count (positive) */ +#define CCW_ADR 017 /* LH: High bits of 22-bit address */ + /* B18-35 Low bits of ditto */ + +/* Channel Status Word +*/ +#define CSW_SET 0400000 /* B0 - Always set 1 */ +#define CSW_MPAR 0200000 /* B1 - Mem Par Err during CCW fetch */ +#define CSW_NOAPE 0100000 /* B2 - Set if NO mem addr par err */ +#define CSW_NOWC0 040000 /* B3 - CCW count NOT 0 when CSW set */ +#define CSW_NXM 020000 /* B4 - Chan referenced NXM */ +#define CSW_LXFE 0400 /* B9 - Last Transfer Error */ +#define CSW_RH20E 0200 /* B10 - RH20 tried to start not-ready chn */ +#define CSW_LWCE 0100 /* B11 - Long Word Count Error */ +#define CSW_SWCE 040 /* B12 - Short Word Count Error */ +#define CSW_OVN 020 /* B13 - Overrun */ +#define CSW_HIADR 017 /* B14-17 High bits of 22-bit CCW addr+1 */ + /* B18-35 Low bits of ditto */ +#define CSW_CLRSET (CSW_SET|CSW_NOAPE) /* Set CSW to this when clearing */ + + +/* RH20 Device Number assignment +*/ +#define DEVRH20(n) (0540+((n)<<2)) + + +/* RH20 CONO bits. +*/ +#define RH20CO_CRAE 04000 /* B24 - Clears RAE and associated PI req */ +#define RH20CO_CMC 02000 /* B25 - Clear Massbus Controller. Resets + ** RH20 and all drives to power-up state. + ** Will hang channel if xfer in progress, + ** unless RCLP also set. + */ +#define RH20CO_TEC 01000 /* B26 - Clear all xfer error indicators */ +#define RH20CO_MBE 0400 /* B27 - MassBus Enable */ +#define RH20CO_RCLP 0200 /* B28 - Reset Cmd List Ptr (inits channel) */ +#define RH20CO_DSCRF 0100 /* B29 - Clears cmd file xfer if hung + ** (ie if previous data xfer caused xfer + ** error, CONI bits 18-23, 26). + */ +#define RH20CO_MEAE 040 /* B30 - Allow Massbus ATTN to generate PI */ +#define RH20CO_ST 020 /* B31 - Stop Transfer. (nothing cleared) */ +#define RH20CO_CCMD 010 /* B32 - Clear Cmd Done, clears CONI bit 32 + ** and associated PI request. + */ +#define RH20CO_PIA 07 /* B33-35 - PI channel assignment (0 = none) */ + + +/* RH20 CONI bits. +*/ +#define RH20CI_DPE 0400000 /* B18 - Data Parity Error */ +#define RH20CI_DEE 0200000 /* B19 - Drive Exception (xfer error) */ +#define RH20CI_LWCE 0100000 /* B20 - Long Word Count Error */ +#define RH20CI_SWCE 040000 /* B21 - Short Word Count Error */ +#define RH20CI_CE 020000 /* B22 - Channel Error */ +#define RH20CI_DR 010000 /* B23 - Drive Response Error (ie no-ex) */ +#define RH20CI_RAE 04000 /* B24 - External Register Address error */ +#define RH20CI_CNR 02000 /* B25 - Channel Ready */ +#define RH20CI_DOE 01000 /* B26 - Data Overrun */ +#define RH20CI_MBE 0400 /* B27 - MassBus Enable */ +#define RH20CI_MBA 0200 /* B28 - MassBus ATTN */ +#define RH20CI_SCRF 0100 /* B29 - Secondary regs loaded */ +#define RH20CI_MBAE 040 /* B30 - Enable PI on RH20CI_MBA */ +#define RH20CI_PCRF 020 /* B31 - Primary regs loaded, xfer in progrs */ +#define RH20CI_CMD 010 /* B32 - Command Done */ +#define RH20CI_PIA 07 /* B33-35 - PIA set by previous CONO */ + + +/* RH20 DATAO/DATAI word format +** If the LR bit of the word is clear, the prep reg is written. +** Otherwise, the RS field selects the register to write, but +** the RS, DRAES, and DS fields are always copied to the prep reg. +** Normally the read and write formats are the same, except for +** a few bits in the word returned from external regs. +*/ + +/* Bits used for writes to prep reg and external regs */ +#define RH20DO_RS 0770000 /* LH: Register Select */ +#define RH20DO_LR 04000 /* LH: Load Register (0 = prep reg) */ +#define RH20DO_DRAES 0400 /* LH: DRAE Suppress */ +#define RH20DO_DS 07 /* LH: Drive Select */ +#define RH20DO_CBEP 0400000 /* RH: Cause even parity error (diag only) */ + +/* Additional external reg bits used for DATAI */ +#define RH20DI_CBPE 01000 /* LH: Control Bus Par Err during DATAI */ +#define RH20DI_TRA 0200 /* LH: Transfer Received */ +#define RH20DI_CPA 0200000 /* RH: State of parity bit on massbus */ +#define RH20DI_ERD 0177777 /* RH: External Register Data (16 bits) */ + +/* Bits used for internal BAR */ +#define RH20DO_ADR 0177777 /* RH: Disk: BlkAdr, Tape: FrameCnt */ + +/* Bits used for internal TCR */ +#define RH20DO_RCLP 02000 /* LH: Reset Channel when xfer started */ +#define RH20DO_SES 0200 /* LH: Store Chan Status at end of xfer */ +#define RH20DO_DTES 0200000 /* RH: Prevent drive errs from stopping xfer */ +#define RH20DO_NBC 0177700 /* RH: Negative block count (sectors/recs) */ +#define RH20DO_MFC 077 /* RH: Drive command code to use for xfer */ + +/* Bits used for internal IVIR */ +# define RH20DO_IVR 0777 /* RH: EPT offset for PI function word */ + + +/* RH20 register definitions: + + The RH20 has provision for 64 (0100) registers. Only the +high 8 of these (070-077) are in the RH20 itself (internal); the others are +registers of the currently selected drive (external). +*/ + +/* RH20 Internal Registers. +** Note: +** There is also a "preparation register" that is set by all DATAOs +** and used to set up the source for a DATAI, although the register +** itself is not directly addressed. +*/ +#define RH20_1ST 070 /* First internal register */ + +#define RH20_SBAR 070 /* R/W Secondary Block Address Reg */ +#define RH20_STCR 071 /* R/W Secondary Transfer Control Reg */ +#define RH20_PBAR 072 /* RO Primary Block Address Reg */ +#define RH20_PTCR 073 /* RO Primary Transfer Control Reg */ +#define RH20_IVIR 074 /* R/W Interrupt Vector Index Reg */ +#define RH20_RR 075 /* RO Read Reg (Diagnostic only) */ +#define RH20_WR 076 /* WO Write Reg (Diagnostic only) */ +#define RH20_DCR 077 /* WO Diagnostic Control Reg (Diags only) */ + + +/* RH20 External Registers. +** These are not necessarily defined or used by the respective drives, +** but do provide a standard framework. +** Those commented as [I2] are external regs that T20 claims are +** device independent. [-2] are apparently unused by KL T20. +** +** RH11 regs not in drive: CS1 (partial), CS2, WC, BA, ATN? +** RH11 drive regs unknown mapping: BUF +*/ + /* RH11 RP07 */ +#define RHR_CSR 0 /* R/W CS1 CS1 Control/command */ +#define RHR_STS 01 /* RO [I2] STS DS Status */ +#define RHR_ER1 02 /* R/W ER1 ER1 Error 1 */ +#define RHR_MNT 03 /* R/W [-2] MNT MR1 Maintenance */ +#define RHR_ATTN 04 /* R/W [I2] ATN AS Attention Summary */ +#define RHR_BAFC 05 /* R/W [I2] ADR DA Block Addr or Frame Cnt */ +#define RHR_DT 06 /* RO [I2] TYP DT Drive Type */ +#define RHR_LAH 07 /* RO LAH LA Cur BlkAdr or R/W ChkChr */ +#define RHR_SN 010 /* RO [-2] SER SN [*] Serial Number */ +#define RHR_OFTC 011 /* R/W OFS OF Offset or TapeControl */ +#define RHR_DCY 012 /* R/W CYL DC Desired Cylinder Addr */ +#define RHR_CCY 013 /* RO [-2] CCY CC Current Cylinder Addr */ +#define RHR_ER2 014 /* R/W ER2 ER2 [*] Error 2 */ +#define RHR_ER3 015 /* R/W ER3 ER3 Error 3 */ +#define RHR_EPOS 016 /* RO POS EC2? ECC Position */ +#define RHR_EPAT 017 /* RO PAT EC2? ECC Pattern */ + +#define RHR_N 020 /* # of RH20 external registers */ + +/* [*] NOTE!! +** There is a discrepancy in the RH20 doc, p. RH/2-20, External +** Register Summary. Register 10 is shown as ER2 and reg 14 as SN, +** which disagrees with virtually everything else I've seen, which +** have reg 10=SN and reg 14=ER2 !!! The following refs all agree +** on this latter interpretation: +** +** T20 RP04 defs in PHYP4.MAC. +** T10 RH20 defs in DEVPRM.MAC. +** RP07 Tech Manual. +** TM03 Tech Manual and T20 defs. +** +** Hence, I've gone with the latter in my above definitions. +** +** NOTE! +** Not clear what is actually returned in ATTN. RH20 doc says all +** drives respond when RS=ATTN, so resulting value is somehow a summary +** of the ATTN bit for all drives. If pattern is same as that for RH11 +** then unit 0 is rightmost bit. From T20 code this appears to be true. +*/ + +#endif /* ifndef RH20_INCLUDED */ diff --git a/src/dvrpxx.c b/src/dvrpxx.c new file mode 100644 index 0000000..c252ef5 --- /dev/null +++ b/src/dvrpxx.c @@ -0,0 +1,2197 @@ +/* DVRPXX.C - Emulates RP/RM disk drives under RH20 for KL10 +*/ +/* $Id: dvrpxx.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dvrpxx.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#include "klh10.h" + +#if !KLH10_DEV_RPXX && CENV_SYS_DECOSF + /* Stupid gubbish needed to prevent OSF/1 AXP compiler from + ** halting merely because compiled file is empty! + */ +static int decosfcclossage; +#endif + +#if KLH10_DEV_RPXX /* Moby conditional for entire file */ + +#include /* For size_t etc */ +#include +#include +#include +#include /* For malloc */ + +#include "kn10def.h" /* This includes OSD defs */ +#include "kn10dev.h" +#include "dvuba.h" +#include "dvrh20.h" +#include "dvrpxx.h" +#include "prmstr.h" /* For parameter parsing */ + +#if KLH10_DEV_DPRPXX +# include "dpsup.h" /* Using device subproc! */ +# include "dprpxx.h" /* Define stuff shared with subproc */ +#endif +#if 1 /* !KLH10_DEV_DPRPXX */ /* For now, include these too */ +# include "wfio.h" /* For word-based file i/o */ +# include "vdisk.h" /* Virtual Disk facilities */ +#endif + +#ifdef RCSID + RCSID(dvrpxx_c,"$Id: dvrpxx.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +#ifndef DVRP_NSUP /* Max # of drives can create */ +# define DVRP_NSUP (8*8) +#endif + +#ifndef DVRP_MAXPATH /* Length of pathname for diskfile */ +# define DVRP_MAXPATH 63 +#endif + +#if 0 +#define DVDEBUG(d) ((d)->rp_dv.dv_debug) +#define DVDBF(d) ((d)->rp_dv.dv_dbf) +#endif + + +int rp_format = VDK_FMT_RAW; /* VDK_DFxxx value */ + /* External needed until becomes config param */ + +#if KLH10_DEV_DPRPXX /* Max I/O operation in bytes */ +# define DPRP_MAXRECSIZ (sizeof(w10_t)*128*DPRP_NSECS_MAX) +#endif + +/* Disk type configuration params. +** All of these numbers assume drives using 18-bit formatting. +** (16-bit formatting has more sectors per track) +*/ +static struct diskconf { + char dcf_name[8]; /* Drive type name (RP06, etc) */ + int dcf_type; /* Type bits for type register */ + int dcf_nwds; /* Words/Sector */ + int dcf_nsec; /* Sectors/Track */ + int dcf_ntrk; /* Tracks/Cylinder */ + int dcf_ncyl; /* Cylinders/Drive (includes maint cyls) */ +} diskconfs[] = { + /* wrd sec trk cyl Cyl+maint totsec totmaintsec */ + { "RP02", 0, 128, 10, 20, 203 }, /* 200+3 40000 40,600 */ + { "RP03", 0, 128, 10, 20, 403 }, /* 400+3 80000 80,600 */ + + { "RP04", RH_DTRP04, 128, 20, 19, 411 }, /* 406+5 154280 156,180 */ + { "RP05", RH_DTRP05, 128, 20, 19, 411 }, /* " " " */ + { "RP06", RH_DTRP06, 128, 20, 19, 815 }, /* 810+5 307800 309,700 */ + { "RP07", RH_DTRP07, 128, 43, 32, 630 }, /* 629+1 865504 866,880 */ + { "RM03", RH_DTRM03, 128, 30, 5, 823 }, /* 821+2 123150 123,450 */ + { "RM02", RH_DTRM02, 128, 30, 5, 823 }, /* " " " */ + { "RM05", RH_DTRM05, 128, 30, 19, 823 }, /* 821+2 467970 469,110 */ + { "RM80", RH_DTRM80, 128, 30, 14, 561 }, /* 559+2 234780 235,620 */ + + { "" } +}; +/* RP07 doc p.6-23 says addressing params are: +** Mode Cyl Head/Track Sector(18) Sector(16) +** Functional: 630 32 43 50 +** Diagnostic: 632 32 43 50 +*/ + +/* In ITS: + RP06 was 812+3 + RP07 was 627+3 + RM03 was 821+2 + RM05 was 820 + RM80 was 556+3 +*/ + + +#ifndef DVRP_DEFAULT_DISK +# define DVRP_DEFAULT_DISK "RP06" +#endif + + +struct rpdev { + struct device rp_dv; + + /* Drive-specific stuff */ + int rp_bit; /* Attention bit */ + int rp_scmd; /* Pending command if any */ +# define NRH20DRVREGS 040 /* 0-037 */ + dvureg_t rp_reg[NRH20DRVREGS]; + + /* Drive config */ + struct diskconf rp_dcf; /* Disk configuration, incl dev type */ + long rp_totsecs; /* Total # sectors on disk */ + int rp_isdyn; /* TRUE if dynamically sized */ + int rp_fmt; /* Data format on real disk, VDK_FMT_xxx */ + int rp_iswrite; /* TRUE if writable, else RO */ + + /* I/O transfer vars, updated to track progress */ + int rp_blkcnt; /* # sectors in total transfer */ + int rp_xfrcnt; /* # subtransfers so far */ + long rp_blkwds; /* # words left to transfer */ + long rp_blklim; /* # words actually allowed to xfer */ + long rp_cyl; /* Current seek pos - must hold > 16 bits */ + int rp_trk, rp_sec; /* Current position in cyl - 8 bits each */ + long rp_blkadr; /* Disk loc as a sector number */ + int rp_isdirect; /* TRUE if current xfer is direct */ + vmptr_t rp_xfrvp; /* If direct, holds ptr to 10-mem */ + + int rp_bufwds; /* Size of buffer in words */ + int rp_bufsec; /* Size of buffer in sectors */ + unsigned char *rp_buff; /* Ptr to buffer (may be in shared mem) */ + + clkval_t rp_iodly; /* Optional usec to delay I/O ops */ + clktmr_t rp_iotmr; /* Timer for above */ + +#if KLH10_DEV_DPRPXX + char *rp_dpname; /* Pathname of executable subproc */ + int rp_dpdma; /* TRUE to use DP DMA if possible */ + struct dp_s rp_dp; /* Handle on dev subprocess */ + struct dprpxx_s *rp_sdprp; /* Ptr to shared memory segment */ + + int rp_state; +# define RPXX_ST_OFF 0 /* Turned off - no subproc */ +# define RPXX_ST_READY 1 /* On and ready for command */ +# define RPXX_ST_BUSY 2 /* Executing some command */ + int rp_dpdbg; /* Initial DP debug value */ + +#else + long rp_rescnt; /* I/O result sector count */ + long rp_reserr; /* I/O result error (if nonzero) */ + struct vdk_unit rp_vdk; /* Virtual Disk unit */ +#endif + char rp_spath[DVRP_MAXPATH+1]; +}; + +#define RPREG(d,r) ((d)->rp_reg[r]) + +static int nrps = 0; /* # of RPs defined */ +struct rpdev /* External for easier debug */ + *dvrpxx[DVRP_NSUP]; /* Table of pointers, for easier debug */ + +static struct rpdev dvrp; /* Make #0 be static, for easier debug */ + +/* Functions provided to device vector */ + +static int rpxx_init(struct device *d, FILE *of); +static void rpxx_reset(struct device *d); +static uint32 rpxx_rdreg(struct device *d, int reg); +static int rpxx_wrreg(struct device *d, int reg, dvureg_t val); +static void rpxx_powoff(struct device *d); +static int rpxx_mount(struct device *d, FILE *f, char *path, char *argstr); + +/* Other exported vectors */ + +#if KLH10_DEV_DPRPXX +static void rpxx_evhsdon(struct device *d, struct dvevent_s *evp); +static void rpxx_evhrwak(struct device *d, struct dvevent_s *evp); +#endif +static int rpxx_timeout(void *); + +/* Completely internal functions */ + +static int rp_conf(FILE *f, char *s, struct rpdev *rp); +static int rp_xmount(struct rpdev *rp); +static void rp_attn(struct rpdev *); +static void rp_clear(struct rpdev *rp); +static void rp_cmdxct(struct rpdev *, unsigned int); +static void rp_delayop(struct rpdev *, int); + +static int rp_ioxfr(struct rpdev *, int); +static void rp_ioend(struct rpdev *); +static int rp_updxfr(struct rpdev *); +static int rp_wrfilbuf(struct rpdev *); +static int rp_rdflsbuf(struct rpdev *); +static void rp_showbuf(struct rpdev *, unsigned char *, vmptr_t, int, int); + +#if KLH10_DEV_DPRPXX +static void rp_dpcmd(struct rpdev *rp, int cmd, size_t arg); +static int rp_dpstart(struct rpdev *rp); +static void rp_dpcmddon(struct rpdev *); +#endif + +/* Configuration Parameters */ + +#define DVRPXX_PARAMS \ + prmdef(RPP_DBG, "debug"), /* Initial debug value */\ + prmdef(RPP_TYP, "type"), /* Drive type (eg RP06) */\ + prmdef(RPP_CYL, "cyl"), /* Drive size: # cylinders/drive */\ + prmdef(RPP_TRK, "trk"), /* Drive size: # tracks/cyl */\ + prmdef(RPP_SEC, "sec"), /* Drive size: # sectors/trk */\ + prmdef(RPP_SN, "sn"), /* Drive Serial number */\ + prmdef(RPP_PATH, "path"), /* Pack pathname - OS file or raw device */\ + prmdef(RPP_FMT, "format"), /* Pack format */\ + prmdef(RPP_RO, "ro"), /* Pack is Read-Only */\ + prmdef(RPP_RW, "rw"), /* Pack is Read/Write (default) */\ + prmdef(RPP_BUF, "bufsiz"), /* Buffer size in words */\ + prmdef(RPP_IODLY,"iodly"), /* Usec to delay I/O operations */\ + prmdef(RPP_DPDBG,"dpdebug"), /* Initial DP debug value */\ + prmdef(RPP_DMA, "dpdma"), /* True to use subproc DMA if possible */\ + prmdef(RPP_DP, "dppath") /* Device subproc pathname */ + +enum { +# define prmdef(i,s) i + DVRPXX_PARAMS +# undef prmdef +}; + +static char *rpprmtab[] = { +# define prmdef(i,s) s + DVRPXX_PARAMS +# undef prmdef + , NULL +}; + + +static int partyp(struct rpdev *, char *); /* Local parsing routines */ +static int parfmt(char *cp, int *afmt); + +/* RP_CONF - Parse configuration string and set defaults. +** At this point, device has just been created, but not yet bound +** or initialized. +** NOTE that some strings are dynamically allocated! Someday may want +** to clean them up nicely if config fails or device is uncreated. +*/ +static int +rp_conf(FILE *f, char *s, struct rpdev *rp) +{ + int i, ret = TRUE; + struct prmstate_s prm; + char buff[200]; + long lval; + + /* First set defaults for all configurable parameters */ + DVDEBUG(rp) = FALSE; + rp->rp_fmt = rp_format; /* For now, use external default */ + rp->rp_iswrite = TRUE; + partyp(rp, DVRP_DEFAULT_DISK); /* Default disk config */ + RPREG(rp, RHR_SN) = /* Serial Number register (BCD) */ + (((1 / 1000)%10) << 12) + | (((6 / 100)%10) << 8) + | (((nrps / 10)%10) << 4) + | (((nrps )%10) ); + rp->rp_bufsec = 4; /* # sectors in buffer */ + rp->rp_bufwds = rp->rp_bufsec /* # words in buffer */ + * rp->rp_dcf.dcf_nwds; + rp->rp_iodly = /* I/O delay */ +#if KLH10_CPU_KS + CLK_USECS_PER_MSEC; /* on KS, default I/O delay to 1ms */ +#else + 0; /* else none */ +#endif + rp->rp_iotmr = NULL; + rp->rp_spath[0] = '\0'; /* No path (default it later) */ +#if KLH10_DEV_DPRPXX + rp->rp_dpdma = TRUE; /* Default is DO use DMA if possible */ + rp->rp_dpname = "dprpxx"; /* Subproc executable */ + rp->rp_dpdbg = FALSE; +#endif + + prm_init(&prm, buff, sizeof(buff), + s, strlen(s), + rpprmtab, sizeof(rpprmtab[0])); + while ((i = prm_next(&prm)) != PRMK_DONE) { + switch (i) { + case PRMK_NONE: + fprintf(f, "Unknown RPXX parameter \"%s\"\n", prm.prm_name); + ret = FALSE; + continue; + case PRMK_AMBI: + fprintf(f, "Ambiguous RPXX parameter \"%s\"\n", prm.prm_name); + ret = FALSE; + continue; + default: /* Handle matches not supported */ + fprintf(f, "Unsupported RPXX parameter \"%s\"\n", prm.prm_name); + ret = FALSE; + continue; + + case RPP_DBG: /* Parse as true/false boolean or number */ + if (!prm.prm_val) /* No arg => default to 1 */ + DVDEBUG(rp) = 1; + else if (!s_tobool(prm.prm_val, &DVDEBUG(rp))) + break; + continue; + + case RPP_TYP: /* Parse as disk type string */ + if (!prm.prm_val) + break; + if (!partyp(rp, prm.prm_val)) + break; + continue; + + case RPP_CYL: /* Parse as decimal number */ + if (!prm.prm_val || !s_todnum(prm.prm_val, &lval)) + break; + if (lval <= 0 || lval > MASK16) { + fprintf(f, "RPXX CYL must be 0 < x < %ld\n", (long)MASK16); + ret = FALSE; + } + rp->rp_dcf.dcf_ncyl = lval; + rp->rp_isdyn = TRUE; + continue; + + case RPP_TRK: /* Parse as decimal number */ + if (!prm.prm_val || !s_todnum(prm.prm_val, &lval)) + break; + if (lval <= 0 || lval > 0377) { + fprintf(f, "RPXX TRK must be 0 < x < %d\n", 0377); + ret = FALSE; + } + rp->rp_dcf.dcf_ntrk = lval; + rp->rp_isdyn = TRUE; + continue; + + case RPP_SEC: /* Parse as decimal number */ + if (!prm.prm_val || !s_todnum(prm.prm_val, &lval)) + break; + if (lval <= 0 || lval > 0377) { + fprintf(f, "RPXX SEC must be 0 < x < %d\n", 0377); + ret = FALSE; + } + rp->rp_dcf.dcf_nsec = lval; + rp->rp_isdyn = TRUE; + continue; + + case RPP_SN: /* Parse as decimal number */ + if (!prm.prm_val || !s_todnum(prm.prm_val, &lval)) + break; + if (lval < 0) { + fprintf(f, "RP SN must be >= 0\n"); + ret = FALSE; + } else + /* Turn last 4 digits into BCD */ + RPREG(rp, RHR_SN) = + (((lval / 1000)%10) << 12) + | (((lval / 100)%10) << 8) + | (((lval / 10)%10) << 4) + | (((lval )%10) ); + continue; + + case RPP_FMT: /* Parse as disk format string */ + if (!prm.prm_val) + break; + if (!parfmt(prm.prm_val, &rp->rp_fmt)) + break; + continue; + + case RPP_RO: + rp->rp_iswrite = FALSE; + continue; + + case RPP_RW: + rp->rp_iswrite = TRUE; + continue; + + case RPP_BUF: /* Parse as decimal number */ + if (!prm.prm_val || !s_todnum(prm.prm_val, &lval)) + break; + if (lval <= 0 || (lval % rp->rp_dcf.dcf_nwds)) { + fprintf(f, "RPXX bufsiz must be multiple of %d\n", + rp->rp_dcf.dcf_nwds); + ret = FALSE; + } else { + rp->rp_bufsec = lval / rp->rp_dcf.dcf_nwds; + rp->rp_bufwds = rp->rp_bufsec * rp->rp_dcf.dcf_nwds; + } + continue; + + case RPP_IODLY: /* Parse as decimal number */ + if (!prm.prm_val || !s_todnum(prm.prm_val, &lval)) + break; + if ((lval < 0) || (lval > CLK_USECS_PER_SEC)) { + fprintf(f, "RPXX I/O delay invalid: %ld\n", lval); + ret = FALSE; + } else { + rp->rp_iodly = lval; + } + continue; + + case RPP_PATH: /* Parse as simple string */ + if (!prm.prm_val) + break; + if (strlen(prm.prm_val) > DVRP_MAXPATH) { + fprintf(f, "RPXX path too long (max %d)\n", DVRP_MAXPATH); + ret = FALSE; + } else + strcpy(rp->rp_spath, prm.prm_val); + continue; + + case RPP_DPDBG: /* Parse as true/false boolean or number */ +#if KLH10_DEV_DPRPXX + if (!prm.prm_val) /* No arg => default to 1 */ + rp->rp_dpdbg = 1; + else if (!s_tobool(prm.prm_val, &(rp->rp_dpdbg))) + break; +#endif + continue; + + case RPP_DMA: /* Parse as true/false boolean */ +#if KLH10_DEV_DPRPXX + if (!prm.prm_val) + break; + if (!s_tobool(prm.prm_val, &rp->rp_dpdma)) + break; +#endif + continue; + + case RPP_DP: /* Parse as simple string */ +#if KLH10_DEV_DPRPXX + if (!prm.prm_val) + break; + rp->rp_dpname = s_dup(prm.prm_val); +#endif + continue; + } + ret = FALSE; + fprintf(f, "RPXX param \"%s\": ", prm.prm_name); + if (prm.prm_val) + fprintf(f, "bad value syntax: \"%s\"\n", prm.prm_val); + else + fprintf(f, "missing value\n"); + } + + /* Param string all done, do followup checks or cleanup */ +#if KLH10_DEV_DPRPXX + if (!cpu.mm_shared) /* If no shared 10 mem, */ + rp->rp_dpdma = FALSE; /* force no DMA. */ +#endif + + /* Set default path for diskfile if none given */ + if (!rp->rp_spath[0]) { + sprintf(rp->rp_spath, "RH20.%s.%d", + rp->rp_dcf.dcf_name, nrps); + } + + /* Helpful checks to avoid shooting self in foot. */ + + /* If using dynamically sized drive, make sure all geometry + parameters were provided! + */ + if (rp->rp_isdyn) { + if (!rp->rp_dcf.dcf_nsec + || !rp->rp_dcf.dcf_ntrk + || !rp->rp_dcf.dcf_ncyl) { + fprintf(f, "RPXX incomplete dynamic geometry: DT %o, %dc %dt %ds\n", + rp->rp_dcf.dcf_type, rp->rp_dcf.dcf_ncyl, + rp->rp_dcf.dcf_ntrk, rp->rp_dcf.dcf_nsec); + return FALSE; + } + fprintf(f, "RPXX dynamic geom: DT %o, %dc %dt %ds\n", + rp->rp_dcf.dcf_type, rp->rp_dcf.dcf_ncyl, + rp->rp_dcf.dcf_ntrk, rp->rp_dcf.dcf_nsec); + } + + /* Ensure the drive serial # isn't duplicated, otherwise TOPS-20 + will think it's a dual-ported drive and get very confused. + Do similar check for diskfile path as well. + */ + for (i = 0; i < nrps; ++i) { /* Step thru all known RP devs */ + struct rpdev *ckrp; + + if (!(ckrp = dvrpxx[i]) || (ckrp == rp)) + continue; + if (RPREG(ckrp, RHR_SN) == RPREG(rp, RHR_SN)) { + fprintf(f, "RPXX serial num duplicated! %d%d%d%d\n", + (RPREG(rp, RHR_SN) >> 12) & 017, + (RPREG(rp, RHR_SN) >> 8) & 017, + (RPREG(rp, RHR_SN) >> 4) & 017, + (RPREG(rp, RHR_SN) ) & 017); + ret = FALSE; + break; + } + if (strcmp(ckrp->rp_spath, rp->rp_spath) == 0) { + fprintf(f, "RPXX path duplicated! \"%s\"\n", rp->rp_spath); + ret = FALSE; + break; + } + } + + return ret; +} + +static int +partyp(struct rpdev *rp, char *cp) +{ + register struct diskconf *dc; + long lval; + + for (dc = diskconfs; dc->dcf_name[0]; ++dc) { + if (s_match(cp, dc->dcf_name) == 2) { + rp->rp_dcf = *dc; /* Found match, won */ + rp->rp_isdyn = FALSE; /* Not dyn sized */ + return TRUE; + } + } + /* Type name not found. See if it's actually a number; + if so, allow using that but make it dynamic. + Parse as octal. + */ + if (!s_tonum(cp, &lval) || (((unsigned long)lval) > RH_DTTYP)) { + return FALSE; /* Unknown disk type */ + } + /* Specified arbitrary type number. Assume dynamically sized as + well; will barf later if no sizes were provided. + */ + rp->rp_isdyn = TRUE; + sprintf(rp->rp_dcf.dcf_name, "DT%lo", lval); + rp->rp_dcf.dcf_type = lval; + rp->rp_dcf.dcf_nwds = 128; + rp->rp_dcf.dcf_nsec = 0; + rp->rp_dcf.dcf_ntrk = 0; + rp->rp_dcf.dcf_ncyl = 0; + return TRUE; +} + + +/* Parse disk format -- something that VDISK understands. +*/ +static char *fmttab[] = { +# define vdk_fmt(i,n,c,s,f,t) n + VDK_FORMATS +# undef vdk_fmt +}; + +static int +parfmt(char *cp, int *afmt) +{ + register int i; + + for (i = 0; i < VDK_FMT_N; ++i) { + if (s_match(cp, fmttab[i]) == 2) { + *afmt = i; /* Found match, won */ + return TRUE; + } + } + return FALSE; /* Unknown disk format */ +} + + +struct device * +dvrp_create(FILE *f, char *s) +{ + register struct rpdev *rp; + + /* Allocate an RP device structure */ + if (nrps >= DVRP_NSUP) { + fprintf(f, "Too many RPs, max: %d\n", DVRP_NSUP); + return NULL; + } + if (nrps == 0) /* Special-case first RP */ + rp = &dvrp; + else { + if (!(rp = (struct rpdev *)malloc(sizeof(struct rpdev)))) { + fprintf(f, "Cannot allocate RP device! (out of memory)\n"); + return NULL; + } + } + dvrpxx[nrps++] = rp; + + /* Various initialization stuff */ + memset((char *)rp, 0, sizeof(*rp)); + + /* Configure drive externals */ + iodv_setnull(&rp->rp_dv); /* Init as nulldev */ + rp->rp_dv.dv_dflags = DVFL_CTLIO | DVFL_DISK; + rp->rp_dv.dv_init = rpxx_init; + rp->rp_dv.dv_reset = rpxx_reset; + rp->rp_dv.dv_rdreg = rpxx_rdreg; + rp->rp_dv.dv_wrreg = rpxx_wrreg; + rp->rp_dv.dv_powoff = rpxx_powoff; + rp->rp_dv.dv_mount = rpxx_mount; + + /* Configure drive internals from parsed string and remember for + ** setting up disk during init. + */ + if (!rp_conf(f, s, rp)) + return NULL; + + return &rp->rp_dv; +} + +/* Init RP-specific stuff +** This is called as final stage of binding device to controller. +*/ +static int +rpxx_init(struct device *d, FILE *of) +{ + register struct rpdev *rp = (struct rpdev *)d; + + rp->rp_bit = 1 << rp->rp_dv.dv_num; /* Set attention bit mask */ + + rp->rp_totsecs = rp->rp_dcf.dcf_ncyl * + (rp->rp_dcf.dcf_ntrk * rp->rp_dcf.dcf_nsec); + RPREG(rp, RHR_OFTC) = 0; /* OFFSET */ + RPREG(rp, RHR_DT) = RH_DTMH /* DRIVE TYPE */ + | rp->rp_dcf.dcf_type; + if (rp->rp_isdyn) { + RPREG(rp, RHR_DT) |= RH_DTDYNGEOM; + RPREG(rp, RHR_EPOS) = rp->rp_dcf.dcf_ncyl-1; + RPREG(rp, RHR_EPAT) = RH_ADRSET(rp->rp_dcf.dcf_ntrk-1, + rp->rp_dcf.dcf_nsec-1); + } + rp->rp_scmd = -1; + +#if KLH10_DEV_DPRPXX + { + register struct dprpxx_s *dprp; + struct dvevent_s ev; + + rp->rp_state = RPXX_ST_OFF; + + if (!dp_init(&rp->rp_dp, sizeof(struct dprpxx_s), + DP_XT_MSIG, SIGUSR1, 0, /* in fr dp */ + DP_XT_MSIG, SIGUSR1, /* out to dp */ + (size_t)rp->rp_bufwds*sizeof(w10_t))) { + if (of) fprintf(of, "RPXX subproc init failed!\n"); + return FALSE; + } + rp->rp_buff = dp_xsbuff(&(rp->rp_dp.dp_adr->dpc_todp), (size_t *)NULL); + + rp->rp_dv.dv_dpp = &(rp->rp_dp); /* Tell CPU where our DP struct is */ + + /* Set up RPXX-specific part of shared DP memory */ + dprp = (struct dprpxx_s *) rp->rp_dp.dp_adr; + rp->rp_sdprp = dprp; + if (rp->rp_dpdma) { /* If have shared mem and want DMA, */ + dprp->dprp_shmid = cpu.mm_physegid; /* tell DP where it is */ + dprp->dprp_dma = TRUE; + } else { + dprp->dprp_shmid = 0; + dprp->dprp_dma = FALSE; + } + dprp->dprp_dpc.dpc_debug = rp->rp_dv.dv_debug; /* Init debug flag */ + if (cpu.mm_locked) /* Lock DP mem if CPU is */ + dprp->dprp_dpc.dpc_flags |= DPCF_MEMLOCK; + + /* Set up config vars */ + dprp->dprp_fmt = rp->rp_fmt; + strncpy(dprp->dprp_devname, rp->rp_dcf.dcf_name, + sizeof(dprp->dprp_devname)-1); + dprp->dprp_ncyl = rp->rp_dcf.dcf_ncyl; + dprp->dprp_ntrk = rp->rp_dcf.dcf_ntrk; + dprp->dprp_nsec = rp->rp_dcf.dcf_nsec; + dprp->dprp_nwds = rp->rp_dcf.dcf_nwds; + + + /* Register ourselves with main KLH10 loop for DP events */ + + ev.dvev_type = DVEV_DPSIG; /* Event = Device Proc signal */ + ev.dvev_arg.eva_int = SIGUSR1; + ev.dvev_arg2.eva_ip = &(rp->rp_dp.dp_adr->dpc_todp.dpx_donflg); + if (!(*rp->rp_dv.dv_evreg)((struct device *)rp, rpxx_evhsdon, &ev)) { + if (of) fprintf(of, "RPXX event reg failed!\n"); + return FALSE; + } + + ev.dvev_type = DVEV_DPSIG; /* Event = Device Proc signal */ + ev.dvev_arg.eva_int = SIGUSR1; + ev.dvev_arg2.eva_ip = &(rp->rp_dp.dp_adr->dpc_frdp.dpx_wakflg); + if (!(*rp->rp_dv.dv_evreg)((struct device *)rp, rpxx_evhrwak, &ev)) { + if (of) fprintf(of, "RPXX event reg failed!\n"); + return FALSE; + } + } + +#else + + if (!vdk_init(&(rp->rp_vdk), (void (*)())NULL, (char *)NULL)) + return FALSE; + + if (!(rp->rp_buff = (unsigned char *) + malloc(rp->rp_bufwds * sizeof(w10_t)))) { + if (of) fprintf(of, "RPXX alloc of %d-word buffer failed!\n", + rp->rp_bufwds); + return FALSE; + } + + /* Set up config vars */ + rp->rp_vdk.dk_format = rp->rp_fmt; + rp->rp_vdk.dk_filename = rp->rp_spath; + rp->rp_vdk.dk_dtype = rp->rp_dcf.dcf_type; + strcpy(rp->rp_vdk.dk_devname, rp->rp_dcf.dcf_name); + rp->rp_vdk.dk_ncyls = rp->rp_dcf.dcf_ncyl; + rp->rp_vdk.dk_ntrks = rp->rp_dcf.dcf_ntrk; + rp->rp_vdk.dk_nsecs = rp->rp_dcf.dcf_nsec; + rp->rp_vdk.dk_nwds = rp->rp_dcf.dcf_nwds; + +#endif + + /* Do standard drive clear - sets "Drive Available" */ + rp_clear(rp); + + /* Mount pack here if specified as init arg? */ + if (rp->rp_spath[0]) { + +#if KLH10_DEV_DPRPXX + if (!rp_dpstart(rp)) { /* Fire up the subproc! */ + if (of) fprintf(of, "RPXX subproc \"%s\" startup failed!\n", + rp->rp_dpname); + return FALSE; + } +#endif + if (!rp_xmount(rp)) { /* Special initial mount */ + if (of) fprintf(of, "RPXX initial mount of \"%s\" failed!\n", + rp->rp_spath); + return FALSE; + } + } + + /* Set up I/O delay timer if needed */ + if (rp->rp_iodly) { + rp->rp_iotmr = clk_tmrget(rpxx_timeout, (void *)rp, rp->rp_iodly); + clk_tmrquiet(rp->rp_iotmr); /* Immediately make it quiescent */ + } + + return TRUE; +} + + +/* RPXX_RESET - reset RP, clear stuff +*/ +static void +rpxx_reset(struct device *d) +{ + register struct rpdev *rp = (struct rpdev *)d; + + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[rpxx_reset]"); + rp_clear(rp); +} + +/* RPXX_POWOFF - Handle "power-off" which usually means the KLH10 is +** being shut down. This is important if using a dev subproc! +*/ +static void +rpxx_powoff(struct device *d) +{ + register struct rpdev *rp = (struct rpdev *)d; + + /* Later could add stuff to pretend controller/slave is off, but for + ** now it suffices just to clean up. + */ +#if KLH10_DEV_DPRPXX + (*rp->rp_dv.dv_evreg)( /* Flush all event handlers for device */ + (struct device *)rp, + NULL, /* No event handler proc */ + (struct dvevent_s *)NULL); + dp_term(&(rp->rp_dp), 0); /* Flush all subproc overhead */ + + rp->rp_state = RPXX_ST_OFF; + rp->rp_sdprp = NULL; /* Clear pointers no longer meaningful */ + rp->rp_buff = NULL; +#endif +} + +/* RPXX_MOUNT - Mount or dismount a disk pack. +** If path is NULL, wants to dismount; argstr is ignored. +** If path is "", just wants status report. +** Else mounting diskfile; argstr if present has keyword params: +** "ro" - Read-Only +** "rw" - Read/Write (default) +** - "raw", "dbh8", etc - indicate disk word format +** Returns: +** 0 - error. Error message already output to stream, if one. +** 1 - action succeeded. +*/ +static int +rpxx_mount(struct device *d, + FILE *f, char *path, char *argstr) +{ + register struct rpdev *rp = (struct rpdev *)d; + int res; + char *opath; + +#if KLH10_DEV_DPRPXX + opath = rp->rp_spath[0] ? rp->rp_spath : NULL; +#else + int prevmount = vdk_ismounted(&(rp->rp_vdk)); /* Get state */ + int vstate = 0; + + if (prevmount) { + opath = rp->rp_vdk.dk_filename; + vstate = vdk_iswritable(&(rp->rp_vdk)) ? 2 : 1; + } +#endif + + if (path && !*path) { + /* Just wants mount status report */ + if (!opath) { + fprintf(f, "No pack mounted.\n"); + return TRUE; + } + fprintf(f, "Current disk pathname is \"%s\", status ", opath); + +#if KLH10_DEV_DPRPXX + switch (rp->rp_state) { + case RPXX_ST_OFF: + fprintf(f, "OFF\n"); + return TRUE; + case RPXX_ST_BUSY: + fprintf(f, "BUSY"); + break; + case RPXX_ST_READY: + fprintf(f, "READY"); + break; + default: + fprintf(f, "<\?\?%d\?\?>", rp->rp_state); + break; + } + if (rp->rp_sdprp->dprp_mol) + fprintf(f, " ONLINE"); + if (rp->rp_sdprp->dprp_wrl) + fprintf(f, " WRITELOCKED"); +#else + switch (vstate) { + case 1: + fprintf(f, "ONLINE WRITELOCKED"); + break; + case 2: + fprintf(f, "ONLINE"); + break; + default: + fprintf(f, "<\?\?%d\?\?>", vstate); + break; + } +#endif + fprintf(f, "\n"); + return TRUE; + } + + /* Unmount any existing tape, and mount new tape if one provided */ + +#if KLH10_DEV_DPRPXX + /* Should this kill the subproc, or wait its turn to send a command? + ** Don't want to hang waiting for rewind to complete! + */ + /* For now, return error if busy (sigh) + */ + if (rp->rp_state == RPXX_ST_BUSY) { + fprintf(f, "Cannot %smount: drive busy\n", (path ? "" : "un")); + return FALSE; + } + if (rp->rp_state == RPXX_ST_OFF) { + /* Subproc not running. If call is just unmounting, that's all, + ** else must start it up so it can handle the mount. + */ + if (!path) { + rp->rp_spath[0] = '\0'; /* Make sure no current pack */ + fprintf(f, "No pack mounted.\n"); + return TRUE; /* OK, no pack mounted */ + } + + if (!rp_dpstart(rp)) /* Fire up the subproc! */ + return FALSE; + } +#endif /* KLH10_DEV_DPRPXX */ + + /* At this point, state should be READY... */ + + if (path) { + /* Process argstr to determine optional params for mount */ + int roflag = FALSE; + int fmt = rp_format; /* For now, default to external */ + int err = FALSE; + size_t plen; + char tokbuf[100]; + + while (s_eztoken(tokbuf, sizeof(tokbuf), &argstr)) { + if (s_match(tokbuf, "ro")==2) roflag = TRUE; + else if (s_match(tokbuf, "rw")==2) roflag = FALSE; + else if (parfmt(tokbuf, &fmt)); + else { + fprintf(f, "Unknown mount option: \"%s\"\n", tokbuf); + err++; + } + } + if (err) + return FALSE; + + /* Plug params into RP struct for use by xmount */ + rp->rp_fmt = fmt; + rp->rp_iswrite = !roflag; + + plen = strlen(path); + if (plen > DVRP_MAXPATH-1) + plen = DVRP_MAXPATH-1; + memcpy(rp->rp_spath, path, plen); /* Remember pathname */ + rp->rp_spath[plen] = '\0'; + + res = rp_xmount(rp); /* Do it! */ + +#if KLH10_DEV_DPRPXX + fprintf(f, "Mount requested: \"%s\"\n", path); +#else + if (res) + fprintf(f, "Pack mounted: \"%s\"\n", path); + else + fprintf(f, "Mount failed for: \"%s\"\n", path); +#endif + + } else { + /* Just unmounting current pack? */ +#if KLH10_DEV_DPRPXX + rp->rp_buff[0] = '\0'; /* Tell DP to unmount */ + rp->rp_scmd = RH_MNOP; /* Conspire with rp_dpcmddon */ + rp_dpcmd(rp, DPRP_MOUNT, (size_t)0); + fprintf(f, "Unmount requested\n"); + return TRUE; +#else + res = vdk_unmount(&(rp->rp_vdk)); + rp_clear(rp); /* Clear drive status, set regs */ + + if (res) + fprintf(f, "Pack unmounted\n"); + else + fprintf(f, "Unmount failed\n"); +#endif /* !KLH10_DEV_DPRPXX */ + } + return res; +} + +static int +rp_xmount(register struct rpdev *rp) +{ +#if KLH10_DEV_DPRPXX + register size_t cnt; + + /* Copy new path into DP comm buffer, including a terminating nul. + */ + cnt = strlen(rp->rp_spath); + rp->rp_buff[0] = (rp->rp_iswrite ? 'W' : 'R'); /* Special prefix */ + + memcpy((char *)(rp->rp_buff+1), rp->rp_spath, cnt); + rp->rp_buff[++cnt] = '\0'; + rp->rp_sdprp->dprp_fmt = rp->rp_fmt; /* Set desired format */ + + /* Do command! And hope for the best... */ + rp->rp_scmd = RH_MNOP; /* Conspire with rp_dpcmddon */ + rp_dpcmd(rp, DPRP_MOUNT, cnt); + return TRUE; +#else + int res; + + rp->rp_vdk.dk_format = rp->rp_fmt; + res = vdk_mount(&(rp->rp_vdk), rp->rp_spath, rp->rp_iswrite); + + rp_clear(rp); /* Clear drive status, set regs */ + + return res; +#endif /* !KLH10_DEV_DPRPXX */ +} + +#if KLH10_DEV_DPRPXX + + +/* RP_DPCMD - Carry out an asynchronous command +*/ +static void +rp_dpcmd(register struct rpdev *rp, int cmd, size_t arg) +{ + register struct dpx_s *dpx = &(rp->rp_dp.dp_adr->dpc_todp); + + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[rp_dpcmd: DP %d, %ld]\r\n", cmd, (long)arg); + + /* First, double-check to be sure it's OK to send a command */ + if (rp->rp_state != RPXX_ST_READY) { + /* Says not ready -- check DP to see if true */ + if (!(rp->rp_state == RPXX_ST_BUSY) || !dp_xstest(dpx)) { + /* Yep, really can't send command now. + ** This shouldn't happen; what to do? + */ + panic("[rp_dpcmd: can't send cmd %d]", cmd); + } + /* Hmmm, state is BUSY but dp_xstest thinks we're OK, so go ahead */ + } + rp->rp_state = RPXX_ST_BUSY; + + dp_xsend(dpx, cmd, arg); /* Send command! */ +} + + +/* RPXX_EVHSDON - Invoked by INSBRK event handling when +** signal detected from DP saying "done" in response to something +** we sent it. +** Basically this means the DP should be ready to accept another +** command. +*/ +static void +rpxx_evhsdon(struct device *d, + register struct dvevent_s *evp) +{ + register struct rpdev *rp = (struct rpdev *)d; + + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[rpxx_evhsdon: %d]", + (int)dp_xstest(&(rp->rp_dp.dp_adr->dpc_todp))); + + rp->rp_state = RPXX_ST_READY; /* Say ready for cmd again */ + rp_dpcmddon(rp); +} + +/* RPXX_EVHRWAK - Invoked by INSBRK event handling when +** signal detected from DP saying "wake up"; the DP is sending +** us something. +** The RPXX will use this to receive notice of unexpected manual events, +** specifically tape being mounted or unmounted. +*/ +static void +rpxx_evhrwak(struct device *d, + register struct dvevent_s *evp) +{ + register struct rpdev *rp = (struct rpdev *)d; + register struct dpx_s *dpx = &(rp->rp_dp.dp_adr->dpc_frdp); + + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[rpxx_evhrwak: %d]", (int)dp_xrtest(dpx)); + + if (dp_xrtest(dpx)) { /* Verify there's a message for us */ + switch (dp_xrcmd(dpx)) { + case DPRP_MOUNT: + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[rpxx_evhrwak: Disk Online!]\r\n"); + +#if 0 /* Any equivalent for this? */ + RPREG(rp, RHR_STS) |= RP_SSSC; /* Set Slave Status Change */ +#endif + rp_attn(rp); + break; + + default: + break; + } + dp_xrdone(dpx); /* just ACK it */ + } +} + + +static int +rp_dpstart(register struct rpdev *rp) +{ + if (rp->rp_state != RPXX_ST_OFF) { + fprintf(DVDBF(rp), "[rp_dpstart: Already running\?\?]\r\n"); + return FALSE; + } + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[rp_dpstart: Starting DP \"%s\"...", + rp->rp_dpname); + if (!dp_start(&rp->rp_dp, rp->rp_dpname)) { + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), " failed!]\r\n"); + else + fprintf(DVDBF(rp), "[rp_dpstart: Start of DP \"%s\" failed!]\r\n", + rp->rp_dpname); + return FALSE; + } + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), " started!]\r\n"); + + rp->rp_state = RPXX_ST_READY; + return TRUE; +} + +#endif /* KLH10_DEV_DPRPXX */ + +/* RP_SSTA - Set Status bits from slave info +*/ +static void +rp_ssta(register struct rpdev *rp) +{ + register unsigned int sts = RPREG(rp, RHR_STS); + + /* Clear bits we'll check */ + sts &= ~(RH_SPIP|RH_SMOL|RH_SWRL|RH_SLBT|RH_SPGM + |RH_SDPR|RH_SDRY|RH_SVV); + + sts |= RH_SDPR; /* Assume drive always there */ + +#if KLH10_DEV_DPRPXX + { + register struct dprpxx_s *dprp = rp->rp_sdprp; + + if (rp->rp_state != RPXX_ST_OFF && dprp) { + /* Drive present, see if ready for commands */ + if (rp->rp_state == RPXX_ST_READY) + sts |= RH_SDRY; /* Drive present & ready for commands */ + + if (dprp->dprp_mol) sts |= RH_SMOL|RH_SVV; /* Medium online */ + if (dprp->dprp_wrl) sts |= RH_SWRL; /* Write-locked */ + } + } +#else + /* MOL,DPR,RDY, and VV must all be present for the drive to be + ** considered "good" by T20. + */ + sts |= RH_SDPR|RH_SDRY; /* Drive Present & Ready */ + if (vdk_ismounted(&(rp->rp_vdk))) + sts |= RH_SMOL|RH_SVV; /* Online and Valid */ + if (!vdk_iswritable(&(rp->rp_vdk))) + sts |= RH_SWRL; /* Write-locked */ +#endif + + RPREG(rp, RHR_STS) = sts; /* Set new value of status register! */ +} + +/* RP_ATTN - Send special attention interrupt +*/ +static void +rp_attn(register struct rpdev *rp) +{ + RPREG(rp, RHR_STS) |= RH_SATN; + RPREG(rp, RHR_ATTN) |= rp->rp_bit; /* For our drive # */ + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[rp_attn: ON]"); + (*rp->rp_dv.dv_attn)(&(rp->rp_dv), 1); /* Assert ATTN */ +} + +/* RP_CLEAR - clear RP drive +*/ +static void +rp_clear(register struct rpdev *rp) +{ + + /* Turn off any attention bit. */ + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[rp_attn: off]"); + (*rp->rp_dv.dv_attn)(&rp->rp_dv, 0); + + rp->rp_scmd = -1; + if (rp->rp_iodly && rp->rp_iotmr) /* Ensure any timer is quiescent */ + clk_tmrquiet(rp->rp_iotmr); + + /* Clear and set Drive bits in CS1. */ + RPREG(rp, RHR_CSR) = RH_XDVA; /* Drive Available */ + + /* Clearing errors may be tricky. Have to ensure that slave + ** errors are reset as well -- if this involves a command to + ** the DP then how to wait for synchronization to happen? + ** May need to have a shared "clear-before-executing-cmd" flag which can + ** be set anytime. + */ +#if KLH10_DEV_DPRPXX + rp->rp_sdprp->dprp_err = 0; /* For now, so rp_ssta doesn't spill beans */ +#endif + RPREG(rp, RHR_STS) = /* RO FS Formatter Status */ + RH_SDPR; /* Drive/formatter Present */ + rp_ssta(rp); /* Set status bits for drive */ +#if 0 + RPREG(rp, RHR_STS) = + RH_SMOL|RH_SDPR|RH_SDRY|RH_SVV; /* Drive Status */ +#endif + + RPREG(rp, RHR_ER1) = 0; /* ERROR 1. */ + switch (rp->rp_dcf.dcf_type) { + case RH_DTRP06: /* RP06 or RP07 */ + case RH_DTRP07: + + case RH_DTRM03: /* These too? Does ER2 even exist? */ + case RH_DTRM80: + + RPREG(rp, RHR_ER2) = 0; /* ERROR 2. */ + RPREG(rp, RHR_ER3) = 0; /* ERROR 3. */ + break; + } + RPREG(rp, RHR_MNT) &= ~(1<<15); /* Clears bit 15 of MNT */ + if (!rp->rp_isdyn) { /* These are re-used for dynsizing */ + RPREG(rp, RHR_EPOS) = 0; /* ECC POSITION. */ + RPREG(rp, RHR_EPAT) = 0; /* ECC PATTERN. */ + } +} + +static uint32 +rpxx_rdreg(struct device *d, int reg) +{ + register struct rpdev *rp = (struct rpdev *)d; + + switch (reg) { + + /* In general, device registers are just read directly */ + case RHR_CSR: /* R/W CS1 Control/command */ + case RHR_STS: /* RO [I2] STS Status */ + case RHR_ER1: /* R/W ER1 Error 1 */ + case RHR_MNT: /* R/W [-2] MNT Maintenance */ + case RHR_ATTN: /* R/W [I2] ATN? Attention Summary */ + case RHR_BAFC: /* R/W [I2] ADR Block Address or Frame Count */ + case RHR_DT: /* RO [I2] TYP Drive Type */ + case RHR_ER2: /* R/W ER2 Error 2 */ + case RHR_OFTC: /* R/W OFS Offset or TapeControl */ + case RHR_DCY: /* R/W CYL Desired Cylinder Addr */ + case RHR_CCY: /* RO [-2] CCY Current Cylinder Addr */ + case RHR_SN: /* RO [-2] SER Serial Number */ + case RHR_ER3: /* R/W ER3 Error 3 */ + case RHR_EPOS: /* RO POS ECC Position */ + case RHR_EPAT: /* RO PAT ECC Pattern */ + break; + +/* According to ITS, only RP06/7 have CCY,ER2,ER3. */ +/* The RM03 and RM80 have ER3, not clear on CCY and ER2. */ + + /* Like above but with special hack for stupid T20 init, + ** which wants to see Look-Ahead changing so it knows that the + ** disk really is spinning. Little does it know... + */ + case RHR_LAH: /* RO LAH Current BlkAdr or R/W ChkChr */ + RPREG(rp, reg) += 0100; /* Use RP07 sector field & mask */ + RPREG(rp, reg) &= 07700; /* Bump field and return it */ + break; + + default: /* Unknown register */ + if (rp->rp_dv.dv_debug) + fprintf(rp->rp_dv.dv_dbf, "[rpxx_rdreg: unknown reg %o]\r\n", reg); + return -1; /* Return error, caller will handle */ + } + + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[rpxx_rdreg: r%o/ %o]\r\n", + reg, RPREG(rp, reg)); + return RPREG(rp, reg); +} + +static int +rpxx_wrreg(struct device *d, + int reg, + register dvureg_t val) +{ + register struct rpdev *rp = (struct rpdev *)d; + + val &= MASK16; + + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[rpxx_wrreg: r%o/ %o = %o]\r\n", + reg, RPREG(rp, reg), val); + + + /* If GO bit is still set, all reg mods are refused except for ATTN */ + if ((RPREG(rp, RHR_CSR) & RH_XGO) && (reg != RHR_ATTN)) { + /* Set RMR error bit (register modif refused); drive is busy. + ** No ATTN generated. + */ + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[rpxx_wrreg: reg mod refused: %o]\r\n", reg); + + RPREG(rp, RHR_ER1) |= RH_1RMR; /* Set RMR error bit */ + RPREG(rp, RHR_STS) |= RH_SERR; /* And composite error */ + return 1; + } + + switch (reg) { + + case RHR_CSR: /* R/W CS1 Control/command */ + /* Set any permissible drive bits */ + RPREG(rp, RHR_CSR) &= ~(RH_XCMD); /* Bits can set */ + RPREG(rp, RHR_CSR) |= (val & RH_XCMD); /* Set em */ + val &= RH_XCMD; + if (val & RH_XGO) + rp_cmdxct(rp, val); /* Perform drive command */ + break; + + case RHR_ATTN: /* R/W [I2] ATN? Attention Summary */ + /* This register is actually intercepted and handled specially + ** by the controller. At this point, only this specific drive + ** is being addressed, so only one bit of information + ** is meaningful; consider the entire value to be either zero + ** or non-zero. + ** If non-zero, turns off the ATTN bit for this drive. + */ + /* Ignores state of GO bit */ + if (val) { /* Any live bits set? */ + RPREG(rp, RHR_ATTN) = 0; /* Yep, turn them off! */ + RPREG(rp, RHR_STS) &= ~RH_SATN; /* Clear status bit */ + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[rp_attn: off]"); + (*rp->rp_dv.dv_attn)(&rp->rp_dv, 0); /* Tell controller it's off */ + } + break; + + /* Regs that can write */ + case RHR_BAFC: /* R/W [I2] ADR Block Address or Frame Count */ + case RHR_OFTC: /* R/W OFS Offset or TapeControl */ + case RHR_DCY: /* R/W CYL Desired Cylinder Addr */ + case RHR_MNT: /* R/W [-2] MNT Maintenance (not supported) */ + RPREG(rp, reg) = val; + break; + + /* Not clear on these regs. RP07 claims RO, but others say R/W */ + case RHR_ER1: /* R/W? ER1 Error 1 */ + case RHR_ER2: /* R/W? ER2 Error 2 */ + case RHR_ER3: /* R/W? ER3 Error 3 */ + RPREG(rp, reg) = val; + break; + + /* Read-Only registers, write is no-op */ + case RHR_STS: /* RO [I2] STS Status */ + case RHR_DT: /* RO [I2] TYP Drive Type */ + case RHR_LAH: /* RO Current BlockAddr or R/W CheckChar */ + case RHR_SN: /* RO [-2] SER Serial Number */ + case RHR_CCY: /* RO [-2] CCY Current Cylinder Addr */ + case RHR_EPOS: /* RO POS ECC Position */ + case RHR_EPAT: /* RO PAT ECC Pattern */ + break; + +/* According to ITS, only RP06/7 have CCY,ER2,ER3. */ +/* The RM03 and RM80 have ER3, not clear on CCY and ER2. */ + + default: /* Unknown register */ + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[rpxx_wrreg: unknown reg %o]\r\n", reg); + + /* If illegal register was selected, sets ILR bit in error reg, but + ** doesn't generate ATTN. + */ + RPREG(rp, RHR_ER1) |= RH_1ILR; /* Set ILR error bit */ + RPREG(rp, RHR_STS) |= RH_SERR; /* And composite error */ + return 0; /* Return error, caller will handle */ + + } + + return 1; +} + + +/* RP_CMDXCT - RP drive command execution +** Note that rp_cmdxct cannot be called unless the GO bit is off! +** +** Only one command can be given +*/ +static void +rp_cmdxct(register struct rpdev *rp, + unsigned int cmd) +{ + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[RP cmd: %o]\r\n", cmd); + + /* The RP07 doc (p. 6-20) claims that giving a command with GO + ** while an error condition exists will always fail (the operation + ** is "inhibited" and GO is not set). + ** The only exceptions are a Drive Clear or Microdiagnostic command. + */ + /* Check for always-legal CLR command */ + if (cmd == RH_MCLR) { + rp_clear(rp); + return; + } + + /* Now check for pre-existing error */ + if (RPREG(rp, RHR_STS) & RH_SERR) { /* Check composite bit */ + RPREG(rp, RHR_CSR) &= ~RH_XGO; /* Turn off GO */ + rp_attn(rp); /* and set ATA to interrupt */ + return; + } + + /* Check for medium online. + ** RP07 doc says MOL must be set prior to initiation of any command + ** except in Microdiag mode, but doesn't say what happens if you try + ** anyway. The following code is similar to that of the TM02/3. + */ + if (!(RPREG(rp, RHR_STS) & RH_SMOL)) { /* No "Medium Online"? */ + /* Must distinguish between I/O commands and everything else. + ** I/O xfers must invoke special handler. + */ + if ((061 <= cmd && cmd <= 067) /* Write function? */ + || (071 <= cmd && cmd <= 077)) { /* Read function? */ + (*rp->rp_dv.dv_iobeg)(&rp->rp_dv, (cmd < 070)); /* Set up xfer */ + (*rp->rp_dv.dv_drerr)(&rp->rp_dv); /* then say error */ + } + /* Always add error bit for "Unsafe" -- can't find any + ** other plausible bit for offline medium. + */ + RPREG(rp, RHR_CSR) &= ~RH_XGO; /* Turn off GO */ + RPREG(rp, RHR_ER1) |= RH_1UNS; /* Unsafe */ + RPREG(rp, RHR_STS) |= RH_SERR; /* Error summary */ + rp_attn(rp); /* Send attention interrupt */ + return; + } + + + /* OK to execute command! + ** Note Drive Clear (RH_MCLR) has already been checked for. + */ + switch (cmd) { + case RH_MNOP: /* No Operation */ + break; /* Done, no ATTN */ + + case RH_MRDP: /* Read-In Preset */ + RPREG(rp, RHR_DCY) = 0; /* DC = Desired Cylinder */ + RPREG(rp, RHR_BAFC) = 0; /* DA = Desired Sector/Track Address */ + RPREG(rp, RHR_OFTC) = 0; /* OF = Offset */ + break; /* Done, no ATTN */ + + case RH_MUNL: /* Unload ("Standby" -- the pack doesn't fly off). */ + break; /* Could put drive softwarily offline? */ + + case RH_MREC: /* Recalibrate */ + /* Recalibrate does a head seek to home position (cyl 0) and + ** triggers ATTN when done. + ** RP07 doc p.6-50 says this takes 500ms (?!) + */ + RPREG(rp, RHR_CCY) = 0; /* CC = Current Cylinder */ + rp_attn(rp); + break; /* Done, turn off GO */ + + case RH_MRLS: /* Drive release (dual port) */ + break; /* No-op since we're never hacking dual stuff */ + + + case RH_MSEK: /* Seek to Cylinder */ + if (RPREG(rp, RHR_DCY) >= rp->rp_dcf.dcf_ncyl) { + RPREG(rp, RHR_ER1) |= RH_1IAE; /* Illegal Address Error */ + RPREG(rp, RHR_STS) |= RH_SERR; /* Error summary */ + } else { + RPREG(rp, RHR_CCY) = RPREG(rp, RHR_DCY); /* Do the seek */ + } + rp_attn(rp); /* Send attention interrupt */ + break; /* Done, turn off GO and return */ + + case RH_MSRC: /* Search to Cylinder, Track, Sector */ + if ((RPREG(rp, RHR_DCY) >= rp->rp_dcf.dcf_ncyl) + || (RH_ATRKGET(RPREG(rp, RHR_BAFC)) >= rp->rp_dcf.dcf_ntrk) + || (RH_ASECGET(RPREG(rp, RHR_BAFC)) >= rp->rp_dcf.dcf_nsec)) { + RPREG(rp, RHR_ER1) |= RH_1IAE; /* Illegal Address Error */ + RPREG(rp, RHR_STS) |= RH_SERR; /* Error summary */ + } else { + RPREG(rp, RHR_CCY) = RPREG(rp, RHR_DCY); /* Do the seek */ + } + rp_attn(rp); /* Send attention interrupt */ + break; /* Done, turn off GO and return */ + + + case RH_MACK: /* Acknowledge mounting of pack (required before I/O) */ + /* RP07 doesn't support this, but so what */ +#if KLH10_DEV_DPRPXX + /* Talk to subproc? */ +#endif + RPREG(rp, RHR_STS) |= RH_SVV; /* Volume now valid */ + break; /* Dunno, but assume no ATTN needed */ + + case RH_MOFS: /* Offset Heads Slightly */ + case RH_MCEN: /* Return Heads To Centerline */ + break; /* Dunno, but assume no ATTN needed */ + + +#if 0 /* Commented out so will be intepreted as illegal functions */ + case RH_MWCF: /* Write Check Header and Data (?doesn't work) */ + case RH_MWHD: /* Write Header And Data (RP07: Format Track) */ + case RH_MWTD: /* Write Track Descriptor (RP07 only) */ + case RH_MRTD: /* Read Track Descriptor (RP07 only) */ + break; +#endif + + + + case RH_MWRT: /* Write Data */ + if (rp->rp_iodly) { /* If config setting is for delay, */ + rp_delayop(rp, RH_MWRT); /* Do delayed op instead */ + } + else if (!rp_ioxfr(rp, 1)) /* Errors handled by rp_io now */ + break; /* Failed, drop out and turn off GO */ + RPREG(rp, RHR_STS) &= ~RH_SDRY; /* Drive busy, note GO still on! */ + return; + +#if KLH10_SYS_ITS /* Pretend this works so ITS Salvager will run */ + case RH_MRHD: /* Read Header and Data */ +#endif + case RH_MWCH: /* Write Check Data (RP07: Same as Read Data) */ + case RH_MRED: /* Read Data */ + if (rp->rp_iodly) { /* If config setting is for delay, */ + rp_delayop(rp, RH_MRED); /* Do delayed op instead */ + } + else if (!rp_ioxfr(rp, 0)) /* Errors handled by rp_io now */ + break; /* Failed, drop out and turn off GO */ + RPREG(rp, RHR_STS) &= ~RH_SDRY; /* Drive busy, note GO still on! */ + return; + + default: + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[rp_cmdxct: unknown cmd %#o]\r\n", cmd); + + RPREG(rp, RHR_CSR) &= ~RH_XGO; /* Turn off GO */ + RPREG(rp, RHR_ER1) |= RH_1ILF; /* Illegal Function code */ + RPREG(rp, RHR_STS) |= RH_SERR; /* Error summary */ + rp_attn(rp); /* Send attention interrupt */ + return; + } + + /* Command done. Default is NOT to set attention bit. */ + RPREG(rp, RHR_CSR) &= ~RH_XGO; /* Turn off GO */ +} + + +/* Called to issue a delayed I/O operation. +** Note that delay applies to STARTING the operation, not to +** reporting its completion via interrupt. We cannot quietly start +** the read and then delay the interrupt; that would work for some +** monitor timing problems, but would not avoid situations where, +** for example, the bootstrap initiates a disk read that overlays the +** currently executing code!! This actually happens in an unpatched ITS. +*/ +static void +rp_delayop(register struct rpdev *rp, int cmd) +{ + if (rp->rp_scmd >= 0) + fprintf(DVDBF(rp), "[rp_delayop: cmd overrun: old %o, new %o]\r\n", + rp->rp_scmd, cmd); + rp->rp_scmd = cmd; /* Save command for rpxx_timeout */ + clk_tmractiv(rp->rp_iotmr); /* Start timer */ +} + +static int +rpxx_timeout(void *arg) +{ + register struct rpdev *rp = (struct rpdev *)arg; + + switch (rp->rp_scmd) { + case -1: /* No command buffered up?! */ + default: + fprintf(DVDBF(rp), "[rpxx_timeout: no cmd?]"); + /* Do nothing whatsoever beyond grumbling */ + break; + + case RH_MWRT: /* Write Data */ + if (!rp_ioxfr(rp, 1)) { + RPREG(rp, RHR_CSR) &= ~RH_XGO; /* Error, turn off GO */ + } + break; + + case RH_MRED: /* Read Data */ + if (!rp_ioxfr(rp, 0)) { + RPREG(rp, RHR_CSR) &= ~RH_XGO; /* Error, turn off GO */ + } + break; + } + return CLKEVH_RET_QUIET; /* Become quiescent */ +} + + +#if KLH10_DEV_DPRPXX + +/* RP_DPCMDDON - Command/operation completed, wrap it up. +** Slave is known to be quiescent at this point. +*/ +static void +rp_dpcmddon(register struct rpdev *rp) +{ + register int cmd = rp->rp_scmd; /* Find command to complete */ + + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[rp_dpcmddon: %#o]\r\n", cmd); + + + switch (cmd) { + + case RH_MNOP: /* No Operation - actually DPRP_MOUNT! */ + /* This should only happen when a DPRP_MOUNT has completed, + ** because nothing else puts a NOP in rp_scmd. Hack. + ** See also rpxx_evhrwak. + */ + rp_ssta(rp); /* update all status */ +#if 0 + RPREG(rp, RHR_STS) |= RP_SSSC; /* Set SSC - slave changed state */ +#endif + rp_attn(rp); + break; + + case RH_MUNL: /* Unload */ + rp_ssta(rp); /* Set status to whatever */ + break; + + case RH_MCLR: /* Formatter clear (reset errors etc.) */ + break; + + /* The following commands are I/O xfer commands */ + case RH_MWRT: /* Write Data */ + /* Data written from buffer, maybe fill it up again */ + if (rp_wrfilbuf(rp)) + return; /* Yep, keep going */ + break; /* Nope, all done, turn off GO bit */ + + case RH_MRED: /* Read Data */ + /* Data read into buffer, dispose of it and maybe get more */ + if (rp_rdflsbuf(rp)) + return; /* Yep, keep going */ + break; /* Nope, all done, turn off GO bit */ + + + default: + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[RPXX unknown scmd %#o]\r\n", cmd); + /* Let unknown commands clear GO bit, to avoid wedging */ + break; + } + + rp->rp_scmd = -1; /* No more pending cmd */ + RPREG(rp, RHR_CSR) &= ~RH_XGO; /* Turn off GO bit in CSR */ + rp_ssta(rp); /* Update all status */ +} + +#endif /* KLH10_DEV_DPRPXX */ + +/* + One complete transfer can involve a number of separate +subtransfers as directed by the controller (data channel), which does +scatter-gather I/O. This makes life harder, because it's not clear +whether to expect a device subproc to be capable of doing transfers of +less than one sector. It's possible, but may be less efficient than +e.g. simply reading in the whole sector into a temporary buffer and then +distributing it from there. + + Such transfers DO happen -- eg when booting up the monitor, +the T20 boot skips over the first 16 words of the first page, to skip +over the AC addresses. + +Ideal case: transferring whole multiple sectors into sequential + memory addresses. +Minimal direct xfer case: transferring whole sector. + +Spiral note: For the RP06/7 at least, T10 expects to be able to do "spiral" + read/write transfers where the drive will automatically seek to the + next cylinder if necessary. + +*/ + +static int +rp_ioxfr(register struct rpdev *rp, int wrtf) +{ + /* Find # sectors controller wants us to xfer */ + rp->rp_blkcnt = (*rp->rp_dv.dv_iobeg)(&rp->rp_dv, wrtf); + if (!rp->rp_blkcnt) { /* If screwed up somehow, */ + (*rp->rp_dv.dv_ioend)(&rp->rp_dv, 0); /* Tell ctlr we're done, */ + return 0; /* and take failure return */ + } + /* Find resulting total # of words */ + rp->rp_blkwds = rp->rp_blkcnt * rp->rp_dcf.dcf_nwds; + + /* Determine & verify initial disk address to read from or write to */ + + rp->rp_cyl = RPREG(rp, RHR_DCY); /* Use "desired" cyl - implied seek! */ + rp->rp_trk = RH_ATRKGET(RPREG(rp, RHR_BAFC)); + rp->rp_sec = RH_ASECGET(RPREG(rp, RHR_BAFC)); + if ( (rp->rp_cyl >= rp->rp_dcf.dcf_ncyl) + || (rp->rp_trk >= rp->rp_dcf.dcf_ntrk) + || (rp->rp_sec >= rp->rp_dcf.dcf_nsec)) { + RPREG(rp, RHR_ER1) |= RH_1IAE; /* Invalid Address Error */ + RPREG(rp, RHR_STS) |= RH_SERR; + (*rp->rp_dv.dv_drerr)(&rp->rp_dv); /* Complain to ctlr */ + rp_attn(rp); /* Trigger ATA */ + return 0; + } + + /* Starting address OK, get it as a sector address */ + + rp->rp_blkadr = (((rp->rp_cyl * rp->rp_dcf.dcf_ntrk) + + rp->rp_trk) * rp->rp_dcf.dcf_nsec + + rp->rp_sec); + + /* Check length of transfer against disk address, to see whether + ** it will overrun some boundary, and set limit accordingly. + ** Currently we assume spiral xfer capability, so don't check for + ** cylinder boundaries other than last one. + */ + if ((rp->rp_blkadr + rp->rp_blkcnt) <= rp->rp_totsecs) + rp->rp_blklim = rp->rp_blkwds; /* OK to xfer all wds */ + else /* Oops, truncate */ + rp->rp_blklim = (rp->rp_totsecs - rp->rp_blkadr) + * rp->rp_dcf.dcf_nwds; + + rp->rp_xfrcnt = 0; /* Init # of subtransfers */ + rp->rp_isdirect = FALSE; /* Not direct xfer (yet) */ + if (wrtf) { + rp->rp_scmd = RH_MWRT; +#if KLH10_DEV_DPRPXX + return rp_wrfilbuf(rp); /* Writing - Fill up buffer, start I/O */ +#else + while (rp_wrfilbuf(rp)); + return 0; +#endif + } else { + rp->rp_scmd = RH_MRED; +#if KLH10_DEV_DPRPXX + return rp_rdflsbuf(rp); /* Reading - Start I/O, empty buffer */ +#else + while (rp_rdflsbuf(rp)); + return 0; +#endif + } +} + +/* RP_IOEND - Called when RP xfer done, when either device or channel +** is out of data, or if error detected in middle. +*/ +static void +rp_ioend(register struct rpdev *rp) +{ + /* Wrap up channel xfer, tell controller we're done */ + + rp_ssta(rp); /* Set status in case MOL etc changed */ + if (RPREG(rp, RHR_STS) & RH_SERR) { + /* Device error - inform controller */ + (*rp->rp_dv.dv_drerr)(&rp->rp_dv); + rp_attn(rp); + + } else if (rp->rp_blkwds) { + /* Channel problem, ran out of space from channel too early. */ + (*rp->rp_dv.dv_ioend)(&rp->rp_dv, /* Say device wanted more */ + (int)((rp->rp_blkwds + rp->rp_dcf.dcf_nwds-1) + / rp->rp_dcf.dcf_nwds)); + } else { + /* Normal termination, device done. + ** Channel will check itself to see whether it had any words left. + */ + (*rp->rp_dv.dv_ioend)(&rp->rp_dv, 0); /* Win, all blks done */ + } +} + +/* RP_UPDXFR - Update vars to reflect disk transfer. +** Returns 0 if should stop (error of some kind) +** Assumes no partial sector reads or writes. +*/ +static int +rp_updxfr(register struct rpdev *rp) +{ + register int i; + +#if KLH10_DEV_DPRPXX + i = rp->rp_sdprp->dprp_scnt; +#else + i = rp->rp_rescnt; +#endif + + rp->rp_blkadr += i; /* Update sector address */ + + if ((rp->rp_sec += i) >= rp->rp_dcf.dcf_nsec) { + i = rp->rp_sec / rp->rp_dcf.dcf_nsec; + rp->rp_sec = rp->rp_sec % rp->rp_dcf.dcf_nsec; + if ((rp->rp_trk += i) >= rp->rp_dcf.dcf_ntrk) { +#if 1 + /* If spiral read/write supported, bump cylinder # as well. + This is supported by at least the RP05, RP06, RP07; the others + are unknown, but for now assume the same and hope nothing + ever depends on non-spiraling. + */ + i = rp->rp_trk / rp->rp_dcf.dcf_ntrk; + rp->rp_trk = rp->rp_trk % rp->rp_dcf.dcf_ntrk; + if ((rp->rp_cyl += i) >= rp->rp_dcf.dcf_ncyl) { + /* If trk/sec NZ, or plan to do more I/O, say AOE */ + if (rp->rp_blkwds || rp->rp_trk || rp->rp_sec) { + RPREG(rp, RHR_ER1) |= RH_1AOE; + RPREG(rp, RHR_STS) |= RH_SERR; + } + } + RPREG(rp, RHR_CCY) = rp->rp_cyl; + RPREG(rp, RHR_DCY) = rp->rp_cyl; +#endif + } + } + RPREG(rp, RHR_BAFC) = RH_ADRSET(rp->rp_trk, rp->rp_sec); + + /* Check to see if last write completed successfully */ +#if KLH10_DEV_DPRPXX + if (i = rp->rp_sdprp->dprp_err) { +#else + if (i = rp->rp_reserr) { +#endif + /* Ugh, what error bit to use?? */ + if (i < 0) { /* If -1 assume addr ovfl */ + RPREG(rp, RHR_ER1) |= RH_1AOE; + } else { + RPREG(rp, RHR_ER1) |= RH_1UNS; /* Use generic "Unsafe" */ + } + RPREG(rp, RHR_STS) |= RH_SERR; /* Set composite error */ + } + + if (RPREG(rp, RHR_STS) & RH_SERR) { + rp_ioend(rp); /* Ugh, stop now */ + return 0; /* Do nothing else */ + } + return 1; +} + +/* RP_WRFILBUF - Set up buffer for writing to disk +*/ +static int +rp_wrfilbuf(register struct rpdev *rp) +{ + register int wc; + register long bwcnt, totw; + register w10_t *wp; + vmptr_t vp; + + if (rp->rp_xfrcnt++) { + + /* Invoking after completion of a write; check results, do next + ** subtransfer. + ** Update registers to reflect any actual I/O done, then + ** see if operation complete or need to write another bufferful. + */ + if (rp->rp_isdirect) { +#if KLH10_DEV_DPRPXX + wc = rp->rp_sdprp->dprp_scnt; /* Get # sectors written */ +#else + wc = rp->rp_rescnt; +#endif + wc *= rp->rp_dcf.dcf_nwds; /* Find # words */ + rp->rp_blkwds -= wc; + rp->rp_blklim -= wc; + (void) (*rp->rp_dv.dv_iobuf)(&rp->rp_dv, wc, &vp); + } + + if (!rp_updxfr(rp)) /* Update regs to reflect progress */ + return 0; /* Ugh, error of some kind, ATA & DC done */ + if (!rp->rp_blkwds) { /* If nothing left to write, */ + rp_ioend(rp); /* stop normally. */ + return 0; + } + } + + /* Get 10's initial data channel info - buffer pointer and count. + ** Use this to determine whether a direct xfer makes sense. + */ + wc = (*rp->rp_dv.dv_iobuf)(&rp->rp_dv, 0, &vp); + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[rp_wrfilbuf: Write %d (of %ld) words]", + wc, rp->rp_blkwds); + +#if KLH10_DEV_DPRPXX + if (rp->rp_dpdma && (wc > rp->rp_dcf.dcf_nwds)) { +#else + if (wc > rp->rp_dcf.dcf_nwds) { +#endif + /* Direct! At least one sector's worth. + */ + totw = (wc < rp->rp_blklim) /* Truncate if needed */ + ? wc : rp->rp_blklim; + wc = (totw / rp->rp_dcf.dcf_nwds); /* Find # sectors */ + rp->rp_isdirect = TRUE; + rp->rp_xfrvp = vp; + + if (DVDEBUG(rp)) { + fprintf(DVDBF(rp), "[RP wrdir: %d sec, %ld <- %#lo]\r\n", + wc, (long)rp->rp_blkadr, + (long)(vp - vm_physmap(0))); + if (DVDEBUG(rp) & DVDBF_DATSHO) + rp_showbuf(rp, (unsigned char *)NULL, vp, (int)totw, 0); + } + +#if KLH10_DEV_DPRPXX + /* Set up shared vars here */ + rp->rp_sdprp->dprp_phyadr = vp - vm_physmap(0); + rp->rp_sdprp->dprp_scnt = wc; /* # sectors */ + rp->rp_sdprp->dprp_daddr = rp->rp_blkadr; + rp_dpcmd(rp, DPRP_WRDMA, (size_t)0 /* wc*128*sizeof(w10_t) */); +#else + rp->rp_rescnt = vdk_write(&rp->rp_vdk, vp, rp->rp_blkadr, wc); + + /* Check for error -- if ran out of space, go offline! */ + if ((rp->rp_reserr = rp->rp_vdk.dk_err) == ENOSPC) { + vdk_unmount(&(rp->rp_vdk)); /* Take offline */ + RPREG(rp, RHR_ER1) |= RH_1UNS; /* Unsafe */ + RPREG(rp, RHR_STS) |= RH_SERR; /* Error summary */ + } +#endif + return 1; + } + + /* Non-direct xfer, must use shared buffer. + ** Xfer is limited by size of shared buffer, but will always + ** be some multiple of sector size. + */ + rp->rp_isdirect = FALSE; + bwcnt = (rp->rp_bufwds < rp->rp_blklim) + ? rp->rp_bufwds : rp->rp_blklim; + totw = bwcnt; + wp = (w10_t *)(rp->rp_buff); + while (wc && bwcnt) { + + if (wc < 0) { /* Channel trying a reverse xfer?? */ + /* Yuck, don't attempt to support this. What err to use? + ** It's not a device error actually... just halt chan xfer. + */ + (*rp->rp_dv.dv_ioend)(&rp->rp_dv, 1); /* Complain to ctlr */ + return 0; + } + + /* WC has # words to xfer on this pass. + ** VP will always be set cuz we're writing ("channel skip" uses a + ** pattern of words, hence VP is never null). + */ + if (wc > bwcnt) /* Limit to # wds in buffer */ + wc = bwcnt; + + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[rp_wrfilbuf: %d %#lo]\r\n", + wc, (long)(vp - vm_physmap(0))); + + memcpy((char *)wp, (char *)vp, sizeof(w10_t)*wc); + if (DVDEBUG(rp) & DVDBF_DATSHO) + rp_showbuf(rp, (unsigned char *)(wp - wc), vp, wc, 0); + bwcnt -= wc; + wp += wc; + + /* Update controller/datachannel's notion of transfer, and set up + ** for next pass. Loop test will fail if WC set 0. + */ + wc = (*rp->rp_dv.dv_iobuf)(&rp->rp_dv, wc, &vp); + } + + /* See if buffer has anything and write it out if so */ + totw -= bwcnt; /* Find # words put into buffer */ + if (totw <= 0) { + rp_ioend(rp); /* Nothing in buffer, stop entire xfer now */ + return 0; + } + + /* Something in buffer -- ensure rounded up to a sector boundary */ + if (bwcnt + && (wc = bwcnt % rp->rp_dcf.dcf_nwds)) { /* Find # words left */ + /* Last sector not completely filled out. Fill it up with + ** zero words, which is what real hardware would do. + */ + memset((char *)wp, 0, wc*sizeof(w10_t)); /* Clear rem */ + bwcnt = totw + wc; /* Find new rounded-up total */ + } else /* Rounded fine, just use totw as is */ + bwcnt = totw; + + + /* Set up for indirect xfer and do it! */ + rp->rp_blkwds -= totw; /* Reflect gobble from channel */ + rp->rp_blklim -= totw; + + wc = (bwcnt / rp->rp_dcf.dcf_nwds); /* Find # sectors */ + +#if KLH10_DEV_DPRPXX + /* Set up shared mem vars here */ + rp->rp_sdprp->dprp_scnt = wc; /* # sectors to write from buffer */ + rp->rp_sdprp->dprp_daddr = rp->rp_blkadr; + rp_dpcmd(rp, DPRP_WRITE, (size_t)0 /* wc*128*sizeof(w10_t) */); +#else + + /* Set up WC and VP for indirect xfer */ + vp = (vmptr_t) rp->rp_buff; /* Pointer to buffer */ + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[RP wrbuf: %d sec, %ld <- 0x%lx]\r\n", + wc, (long)rp->rp_blkadr, (long)vp); + rp->rp_rescnt = vdk_write(&rp->rp_vdk, vp, rp->rp_blkadr, wc); + + /* Check for error -- if ran out of space, go offline! */ + if ((rp->rp_reserr = rp->rp_vdk.dk_err) == ENOSPC) { + vdk_unmount(&(rp->rp_vdk)); /* Take offline */ + RPREG(rp, RHR_ER1) |= RH_1UNS; /* Unsafe */ + RPREG(rp, RHR_STS) |= RH_SERR; /* Error summary */ + } +#endif + + return 1; /* Say to keep going... */ +} + +/* RP_RDFLSBUF - Flush record buffer by copying it into 10's memory, +** and ask for another bufferful. +*/ +static int +rp_rdflsbuf(register struct rpdev *rp) +{ + register int wc; + register long bwcnt, totw; + register w10_t *wp; + vmptr_t vp; + + if (rp->rp_xfrcnt++ == 0) { + + /* First time - Get initial WC request from channel. + ** WC will have # words to xfer (may be 0 if failed or all done, or + ** negative if channel is trying to do a reverse transfer.) + ** If VP is set, then it points to phys mem to xfer to/from. + */ + wc = (*rp->rp_dv.dv_iobuf)(&rp->rp_dv, 0, &vp); + + } else { + + /* Invoking after completion of a read. + ** Examine result, copy into 10 mem, and set up for next. + */ +#if KLH10_DEV_DPRPXX + totw = rp->rp_sdprp->dprp_scnt; /* Find # sectors read */ +#else + totw = rp->rp_rescnt; /* Find # sectors read */ +#endif + totw *= rp->rp_dcf.dcf_nwds; /* Find # words read */ + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[rp_rdflsbuf: Read %ld words]", totw); + + if (rp->rp_isdirect) { + /* Just completed a direct transfer, needn't copy from buffer! + ** Can simply update channel vars. + */ + if (DVDEBUG(rp) & DVDBF_DATSHO) + rp_showbuf(rp, (unsigned char *)NULL, rp->rp_xfrvp, (int)totw, 0); + wc = (*rp->rp_dv.dv_iobuf)(&rp->rp_dv, (int)totw, &vp); + + } else { + /* Indirect transfer, must copy from buffer into 10's memory. + ** Get 10's current buffer pointer and count + */ + wc = (*rp->rp_dv.dv_iobuf)(&rp->rp_dv, 0, &vp); + wp = (w10_t *)rp->rp_buff; /* Get ptr to buffer */ + bwcnt = totw; + for (; wc && bwcnt; ) { + if (wc < 0) { /* Channel trying a reverse xfer?? */ + /* Yuck, don't attempt to support this. What err to use? + ** It's not a device error actually... just stop channel. + */ + if (!rp_updxfr(rp)) /* Update xfer so far */ + return 0; + (*rp->rp_dv.dv_ioend)(&rp->rp_dv, 1); /* Tell ctlr */ + return 0; + } + + if (wc > bwcnt) /* Apply block wdcount limit */ + wc = bwcnt; + + if (vp) { /* VP may be NULL if skipping */ + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[rp_rdflsbuf: %d %#lo]\r\n", + wc, (long)(vp - vm_physmap(0))); + memcpy((char *)vp, (char *)wp, sizeof(w10_t)*wc); + if (DVDEBUG(rp) & DVDBF_DATSHO) + rp_showbuf(rp, (unsigned char *)wp, vp, wc, 0); + } else + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[rp_rdflsbuf: skip %d]\r\n", wc); + + bwcnt -= wc; /* Update block wdcnt */ + wp += wc; /* and read pointer from buffer */ + + /* Update controller/datachannel's notion of transfer, and set + ** up for next pass. Loop test will fail if WC set 0. + */ + wc = (*rp->rp_dv.dv_iobuf)(&rp->rp_dv, wc, &vp); + } + + totw -= bwcnt; /* Find total # words copied */ + } + + /* Disk input all copied into 10 mem, or no more space from channel. + ** See if need to read more from disk. + */ + rp->rp_blkwds -= totw; + rp->rp_blklim -= totw; + + if (!rp_updxfr(rp)) /* Update regs, check for error */ + return 0; + + if (!wc || !rp->rp_blkwds) { /* Ran out in either chan or dev? */ + rp_ioend(rp); /* Yup, stop xfer */ + return 0; + } + } + + /* First time, or more to do. + ** Set up and initiate read, using remaining block count. + ** WC has # words channel wants for a direct xfer; VP is set if xfer + ** is to memory (else skipping). + */ + if ((bwcnt = rp->rp_blklim) == 0) { /* If no more to read, */ + rp_ioend(rp); /* terminate normally */ + return 0; + } + + /* See if OK to do a direct xfer, and + ** see how many words we can xfer on this pass. + */ + if (vp && (wc > rp->rp_dcf.dcf_nwds) +#if KLH10_DEV_DPRPXX + && rp->rp_dpdma +#endif + ) { + /* Yup, at least one sector's worth. */ + rp->rp_isdirect = TRUE; + wc = (wc < bwcnt) ? wc : bwcnt; /* Truncate if needed */ + wc = (wc / rp->rp_dcf.dcf_nwds); /* Find # sectors */ + rp->rp_xfrvp = vp; /* Remember loc reading into */ + +#if KLH10_DEV_DPRPXX + rp->rp_sdprp->dprp_phyadr = vp - vm_physmap(0); +#endif + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[RP rddir: %d sec, %ld -> %#lo]\r\n", + wc, (long)rp->rp_blkadr, (long)(vp - vm_physmap(0))); + + } else { + + /* Non-direct xfer, must use shared buffer. + ** Xfer is limited by size of shared buffer, but will always + ** be some multiple of sector size. + ** Set up WC and VP for indirect xfer. + */ + rp->rp_isdirect = FALSE; + bwcnt = (rp->rp_bufwds < bwcnt) ? rp->rp_bufwds : bwcnt; + wc = (bwcnt / rp->rp_dcf.dcf_nwds); /* Find # sectors */ + vp = (vmptr_t) rp->rp_buff; /* Pointer to buffer */ + + if (DVDEBUG(rp)) + fprintf(DVDBF(rp), "[RP rdbuf: %d sec, %ld -> 0x%lx]\r\n", + wc, (long)rp->rp_blkadr, (long)vp); + } + +#if KLH10_DEV_DPRPXX + rp->rp_sdprp->dprp_scnt = wc; + rp->rp_sdprp->dprp_daddr = rp->rp_blkadr; + rp_dpcmd(rp, (rp->rp_isdirect ? DPRP_RDDMA : DPRP_READ), + (size_t)0 /* wc*128*sizeof(w10_t) */); +# if 0 + dp_xswait(&(rp->rp_dp.dp_adr->dpc_todp)); /* Synch hack */ +# endif +#else + + rp->rp_rescnt = vdk_read(&rp->rp_vdk, vp, rp->rp_blkadr, wc); + rp->rp_reserr = rp->rp_vdk.dk_err; +#endif + + return 1; +} + + +static void +rp_showbuf(register struct rpdev *rp, + register unsigned char *ucp, + register vmptr_t vp, + register int wc, + int fmt) +{ + register w10_t w; + int fpw; + + fpw = fmt; /* for now */ + + for (; --wc >= 0; ++vp) { + w = vm_pget(vp); + fprintf(DVDBF(rp), "[RPXX: %#lo/ %6lo,,%6lo", + (long)(vp - vm_physmap(0)), (long)LHGET(w), (long)RHGET(w)); + if (fpw) { + register int i = fpw; + + fprintf(DVDBF(rp), " "); + while (--i >= 0) + fprintf(DVDBF(rp), " %3o", *ucp++); + } + fprintf(DVDBF(rp), "]\r\n"); + } +} + +#endif /* KLH10_DEV_RPXX */ diff --git a/src/dvrpxx.h b/src/dvrpxx.h new file mode 100644 index 0000000..e922c9d --- /dev/null +++ b/src/dvrpxx.h @@ -0,0 +1,373 @@ +/* DVRPXX.H - RP/RM Disk Drive definitions under RH20 for KL +*/ +/* $Id: dvrpxx.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dvrpxx.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ +/* +** Portions of this file were derived from AI:SYSTEM;RH11 DEFS48 +*/ + +#ifndef DVRPXX_INCLUDED +#define DVRPXX_INCLUDED 1 + +#ifdef RCSID + RCSID(dvrpxx_h,"$Id: dvrpxx.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Initialization & sole entry point for RPXX driver +*/ +#include "kn10dev.h" +extern struct device *dvrp_create(FILE *f, char *s); + + +/* Historical disk physical parameter definitions. + + Cyl/Unit + Type wd/sec sec/trk trk/cyl T20 ITS MaxSecs + RP04/5 128 20 19 400 - 152,000 + RP06 128 20 19 800 812+3 309,700 + RP07 128 43 32 629 627+3 866,880 + RM03 128 30 5 820 820+3 123,450 + RM05 128 30 19 820 - 467,400 + RM80 128 30 14 - 556+3 234,780 +Old RP: + RP02 128 10 20 - 200+3 40,600 + RP03 128 10 20 - 400+3 80,600 +Old RH10: + RP04 128 20 19 - 406+5 156,180 + +Note that sectors of a disk formatted for the PDP-10 have 576 bytes/sector +instead of the (nowadays) more common 512 byte/sector format. The actual +size of an emulated sector on a real disk will depend on the emulation format +selected. + +For the basic case of 8 bytes per 36-bit word, one 128-word emulated +sector will occupy 1024 bytes of real disk space (2 real sectors). +Unless using a raw disk interface, an underlying OS using a 1K or larger +block size should be able to preserve atomicity and handle any bad blocks. + +*/ + + +/* CS1: (RH/DR) CTRL AND STATUS 1. (RH11:0, RH20:0) */ + +# define RH_XSC (1<<15) /* Special Condition */ +# define RH_XTRE (1<<14) /* Transfer Error */ +# define RH_XMCP (1<<13) /* Mass I/O Control Bus Parity Error */ +# define RH_XDVA (1<<11) /* Drive Available */ +# define RH_XPSE (1<<10) /* Port Select */ +# define RH_XA17 (1<<9) /* UB Address Extension Bit 17 */ +# define RH_XA16 (1<<8) /* UB Address Extension Bit 16 */ +# define RH_XRDY (1<<7) /* Ready (for next command) */ +# define RH_XIE (1<<6) /* Interrupt Enable */ +# define RH_XCMD 077 /* Bits 1-5 specify commands. */ +# define RH_XGO (1<<0) /* GO bit */ + + +/* Commands with bit 0 (GO) included: */ +/* "2" Marks those that T20 knows about; "I" for ITS */ + +#define RH_MNOP 01 /* I No Operation */ +#define RH_MUNL 03 /* I2 Unload ("Standby"- the pack doesn't fly off). */ + /* RP07: doesn't have this */ +#define RH_MSEK 05 /* I2 Seek to Cylinder */ +#define RH_MREC 07 /* I2 Recalibrate */ +#define RH_MCLR 011 /* I2 Drive clear (reset errors etc.) */ +#define RH_MRLS 013 /* I2 Drive release (dual port) */ +#define RH_MOFS 015 /* 2 Offset Heads Slightly */ + /* Just sets RH_SOFS on RP07 */ +#define RH_MCEN 017 /* 2 Return Heads To Centerline */ + /* Just clears RH_SOFS on RP07 */ +#define RH_MRDP 021 /* 2 Read-In Preset */ +#define RH_MACK 023 /* Acknowledge mounting of pack (reqrd before I/O) */ + /* RP07: No-op */ + /* 25, 27 unused */ +#define RH_MSRC 031 /* I2 Search (for r.p.s.) */ + /* 33 - unused */ + /* 35 - RP07: Microdiagnostic Command */ + /* 37-47 unused */ +#define RH_MWCH 051 /* I Write Check Data (?doesn't work) */ +#define RH_MWCF 053 /* I Write Check Header & Data (?doesn't work) */ +#define RH_MWRT 061 /* I2 Write Data */ +#define RH_MWHD 063 /* I2 Write Header And Data (RP07: format track) */ +#define RH_MWTD 065 /* I Write Track Descriptor (RP07 only) */ + /* 67 unused */ +#define RH_MRED 071 /* I2 Read Data */ +#define RH_MRHD 073 /* I2 Read Header and Data */ +#define RH_MRTD 075 /* I Read Track Descriptor (RP07 only) */ + + +/* WC: (RH) WORD COUNT. (RH11:1, RH20:-) */ +/* BA: (RH) UNIBUS ADDRESS. (RH11:2, RH20:-) */ + +/* ADR: (DR) DESIRED ADDRESS. (RH11:3, RH20:5) */ +# define RH_ATRK 037400 /* Track */ +# define RH_ASEC 000177 /* Sector */ +/* These are the maximum fields; actual drives use smaller fields but + all are in the same position, and virtually all monitor code manages them + as ((Track<<8)+Sector). The RP07 (for example) uses a 6-bit track + and 7-bit sector, which in reality are 5 bits track and 6 bits sector + as the high bit is only used to help detect overruns or bad addresses. +*/ +# define RH_ATRKGET(a) ((((unsigned)(a))>>8) & (((unsigned)RH_ATRK)>>8)) +# define RH_ASECGET(a) ((a)&RH_ASEC) +# define RH_ADRSET(t,s) ((((t)&0377)<<8) | ((s)&0377)) + +/* CS2: (RH) CTRL AND STATUS 2. (RH11:4, RH20:-) */ + +# define RH_YDLT (1<<15) /* Data Late */ +# define RH_YWCE (1<<14) /* Write Check Error */ +# define RH_YPE (1<<13) /* Parity Error */ +# define RH_YNED (1<<12) /* Non-existant Drive */ +# define RH_YNEM (1<<11) /* BA reg is NXM during DMA */ +# define RH_YPGE (1<<10) /* Program Error */ +# define RH_YMXF (1<<9) /* Missed Transfer */ +# define RH_YMDP (1<<8) /* Mass Data Bus Parity Error */ +# define RH_YOR (1<<7) /* Output Ready (for Silo buffer diag.) */ +# define RH_YIR (1<<6) /* Input Ready (for Silo buffer diag.) */ +# define RH_YCLR (1<<5) /* Controller Clear */ +# define RH_YPAT (1<<4) /* Parity Test */ +# define RH_YBAI (1<<3) /* Unibus Address Increment Inhibit */ +# define RH_YDSK 07 /* Bits 2-0 are the Unit Select. */ + +/* STS: (DR) DRIVE STATUS. (RH11:5, RH20:1) */ + +# define RH_SATN (1<<15) /* Attention Active */ +# define RH_SERR (1<<14) /* Error */ +# define RH_SPIP (1<<13) /* Positioning In Progress */ +# define RH_SMOL (1<<12) /* Medium On-Line */ +# define RH_SWRL (1<<11) /* Write Locked */ +# define RH_SLBT (1<<10) /* Last Block (sector) Transferred */ +# define RH_SPGM (1<<9) /* Programmable (dual port) */ +# define RH_SDPR (1<<8) /* Drive Present */ +# define RH_SDRY (1<<7) /* Drive Ready */ +# define RH_SVV (1<<6) /* Volume Valid */ + /* All the above bits are used by T20. + ** MOL,DPR,RDY, and VV must all be present for the drive to be + ** considered "good". + */ +#if 0 /* RP04-only bits in STS */ +# define RH_SDE1 (1<<5) /* Difference Equals 1 */ +# define RH_SL64 (1<<4) /* Difference Less Than 64 */ +# define RH_SGRV (1<<3) /* Go Reverse */ +# define RH_SDIG (1<<2) /* Drive To Inner Guard Band */ +# define RH_SF20 (1<<1) /* Drive Forward 20in/sec */ +# define RH_SF5 (1<<0) /* Drive Forward 5in/sec */ +#endif /* RP04 */ +#if 0 /* RP07-only bits in STS */ +# define RH_SILV (1<<2) /* Interleaved Sectors */ +# define RH_SEWN (1<<1) /* Early Warning */ +# define RH_SOM (1<<0) /* Offset Mode */ +#endif /* RP07 */ + +/* ER1: (DR) ERROR 1. (RH11:6, RH20:2) */ + +# define RH_1DCK (1<<15) /* Drive Data Check */ +# define RH_1UNS (1<<14) /* Drive Unsafe */ +# define RH_1OPI (1<<13) /* Operation Incomplete */ +# define RH_1DTE (1<<12) /* Drive Timing Error */ +# define RH_1WLK (1<<11) /* Write Lock Error */ +# define RH_1IAE (1<<10) /* Invalid Address Error */ +# define RH_1AOE (1<<9) /* Address Overflow Error */ +# define RH_1CRC (1<<8) /* Header CRC Error */ +# define RH_1HCE (1<<7) /* Header Compare Error */ +# define RH_1ECH (1<<6) /* ECC Hard Error */ +# define RH_1WCF (1<<5) /* Write Clock Fail */ +# define RH_1FER (1<<4) /* Format Error */ +# define RH_1PAR (1<<3) /* Control Bus Parity Error */ +# define RH_1RMR (1<<2) /* Register Modification Refused */ +# define RH_1ILR (1<<1) /* Illegal Register */ +# define RH_1ILF (1<<0) /* Illegal Function */ +/* RH_1AOE is set if drive attempts to spiral-read past end of disk. +** DCK,FER,CRC,HCE inspire T20 to attempt head offsetting. +*/ + +/* ATN: (DR*) ATTENTION SUMMARY. (RH11:7, RH20:4) */ + /* Each bit 7-0 corresponds to a drive asserting ATA. */ + /* Bit 1.1 is Drive #0, 1.2 is drive #1, etc */ + +/* LAH: (DR) LOOK AHEAD. (RH11:010, RH20: 7) */ + /* 2.2 - 1.7 Sector Count. */ + /* 1.6 - 1.5 Encoded Extension Field. */ + +/* BUF: (DR) DATA BUFFER. (RH11:011, RH20:-) */ +/* MNT: (DR) MAINTENANCE. (RH11:012, RH20:3) */ + +/* TYP: (DR) DRIVE TYPE. (RH11:013, RH20:6) */ + +# define RH_DTNBA (1<<15) /* 2.7 Not block (sector) addressed */ +# define RH_DTTAP (1<<14) /* 2.6 Tape */ +# define RH_DTMH (1<<13) /* 2.5 Moving Head (better be a 1!!) */ + /* 2.4 unused */ +# define RH_DTRQ (1<<11) /* 2.3 Drive Request Required (dual port) */ + /* 2.2 unused */ +# define RH_DTDYNGEOM (1<<9) /* 2.1 Dynamic Geometry - KLH10 ONLY!! [*1] */ +# define RH_DTTYP 0777 /* 1.9 - 1.1 Drive Type Number: */ +# define RH_DTRP04 020 /* RP04 */ +# define RH_DTRP05 021 /* RP05 */ +# define RH_DTRP06 022 /* RP06 */ +# define RH_DTRM03 024 /* RM03 */ +# define RH_DTRM02 025 /* RM02 (slow RM03) */ +# define RH_DTRM80 026 /* RM80 */ +# define RH_DTRM05 027 /* RM05 */ +# define RH_DTRP07 042 /* RP07 ("Fixed" - 041 is "Moving") */ + /* Types 020-042 inclusive are "RP04" types */ +# define RH_DTDXB 061 /* DX20B/RP20 */ +/* [*1] !!!NOTE!!! + Bit 01000 is a special KLH10 addition that does not exist in any real + hardware. It is used to support larger drives without having + to invent scores of new drive types. + If set, it indicates that the drive is "dynamically sized" + and certain drive registers can be read to obtain the actual desired disk + geometry: + ECC POS: re-used to hold Maximum Cylinder Value. This is one less than + the number of cylinders for the drive. + ECC PAT: re-used to hold Maximum Track and Sector values, each of which + are likewise one less than their respective numbers. + For example, for a drive geometry of 32K cylinders, 32 tracks, 64 sectors, + the values would be 32K-1, 31, and 63. + Maximum possible values for each field are: + Cylinder: 16 bits = 0177777 + Track: 8 bits = 0377 (but note most monitors assume 5 bits) + Sector: 8 bits = 0377 + */ + +/* SER: (DR) SERIAL NUMBER. (RH11:014, RH20:010) */ + +/* OFS: (DR) OFFSET. (RH11:015, RH20:011) */ +/* From ITS: + ; 2.9-2.8 Unused + ; 2.7 Sign Change (RP06 only) + ; 2.7 Command Modifier (RP07 only) + ; Must be set before RH_MWHD, RH_MWTD or RH_MRTD + ; 2.6 Move Track Descriptor (RP07 only) + ; 0 = 128. bit track descriptor + ; 1 = 344. bit track descriptor + ; T20 uses? ; 2.4 Format Bit (1=16, 0=18) + ; T20 uses? ; 2.3 ECC Inhibit + ; 2.2 Header Compare Inhibit + ; 2.1 Skip Sector Inhibit (RM 16bit only) + ; 1.9 Unused + ; + ; 1.8 - 1.1 Unused on RP07 + ; RP07 doesn't support offsets + ; + ; RP06 Offsets + ; 1.8 - 1.1 Offset Info + ; +400 u" 00010000 + ; -400 u" 10010000 + ; +800 u" 00100000 + ; -800 u" 10100000 + ; +1200 u" 00110000 + ; -1200 u" 10110000 + ; Centerline 00000000 + ; + ; RMxx Offsets + ; 1.1-1.7 Unused + ; 1.8 Offset Direction + ; 0 - Away from spindle + ; 1 - Towards spindle +*/ + +/* CYL: (DR) DESIRED CYLINDER. (RH11:016, RH20:012) */ + +#if 0 /* RP06-specific drive regs and defs */ + /* CCY: (DR) CURRENT CYL. (RH11:017, RH20:013) */ + /* ER2: (DR) ERROR 2. (RH11:020, RH20:014) */ +# define RH_2NHS (1<<10) /* No Head Selection */ +# define RH_2WRU (1<<8) /* Write Ready Unsafe */ + /* ER3: (DR) ERROR 3. (RH11:021, RH20:015) */ +# define RH_3OFC (1<<15) /* Off Cylinder */ +# define RH_3SKI (1<<14) /* SeekIncomplete (also sets UNS+ATA+PIP+RDY)*/ +# define RH_3DCL (1<<6) /* DC power low (or perhaps AC?) */ +# define RH_3ACL (1<<5) /* AC power low (or perhaps DC?) */ + /* (the documentation is confused about */ + /* which is which.) */ + /* T20 checks OFC,SKI */ +#endif /* RP06P */ + +#if 0 /* RP07-specific drive regs and defs */ + /* CCY: (DR) CURRENT CYL. (RH11:017, RH20:013) */ + /* ER2: (DR) ERROR 2. (RH11:020, RH20:014) */ +# define RH_2PRG (1<<15) /* Program Error */ +# define RH_2CRM (1<<14) /* Control ROM parity error */ +# define RH_2H88 (1<<13) /* 8080 in drive is hung */ +/* DEC calls the following three bits READ/WRITE UNSAFE 1, 2 and 3. */ +# define RH_2WU3 (1<<12) /* Write current when no write in progress */ +# define RH_2WU2 (1<<11) /* More than one head selected */ +# define RH_2WU1 (1<<10) /* No write transitions during a write */ +# define RH_2WOV (1<<9) /* Write Overrun */ +# define RH_2WRU (1<<8) /* Write Ready Unsafe */ +# define RH_2COD 0377 /* Error Code */ +/* Error codes are: + 012 Seek operation too long. + 013 Guard band detected during seek operation. + 014 Seek operation overshoot. + 104 Guard band detection failure during recalibrate operation. + 105 Reference gap or guard band pattern detection failure during + recalibrate operation. + 106 Seek error during recalibrate operation. + 112 Heads have attempted to land on guard band during recalibrate + operation. +*/ + /* ER3: (DR) ERROR 3. (RH11:021, RH20:015) */ +# define RH_3BDS (1<<15) /* Bad Sector */ +# define RH_3SKI (1<<14) /* Seek Incomplete (see error code in ER2) */ +# define RH_3DSE (1<<13) /* Defect Skip Error */ +# define RH_3WCF (1<<12) /* Write Current Failure */ +# define RH_3LCF (1<<11) /* Logic Control Failure */ +# define RH_3LBC (1<<10) /* Loss of Bit Clock */ +# define RH_3LCE (1<<9) /* Loss of Cylinder Error */ +# define RH_3X88 (1<<8) /* 8080 in drive failed to respond to a command */ +# define RH_3DCK (1<<7) /* Device Check */ +# define RH_3WHD (1<<6) /* Index Unsafe (Bad RH_MWHD) */ +# define RH_3DCL (1<<5) /* DC Low voltage */ +# define RH_3SDF (1<<4) /* Serdes Data (data buffer timing) Failure */ +# define RH_3PAR (1<<3) /* Data Parity Error during write operation */ +# define RH_3SYB (1<<2) /* Sync Byte error */ +# define RH_3SYC (1<<1) /* Sync Clock failure */ +# define RH_3RTM (1<<0) /* Run timeout */ +#endif /* RP07P */ + +/* RM has no Current Cylinder Register, there is no Error 2 Register + Some of the Error 3 bits differ from those for the RP07. + Q: Should the two unused regs even be defined? Should they cause + IO page failure errors if referenced? +*/ +#if 0 /* RM03|RM80 drive regs and defs */ + /* CCY: (DR) CURRENT CYL. (RH11:017, RH20:013) (not used) */ + /* ER2: (DR) ERROR 2. (RH11:020, RH20:014) (not used) */ + /* ER3: (DR) ERROR 3. (RH11:021, RH20:015) */ +# define RH_3BSE (1<<15) /* Bad Sector (sector marked bad on disk) */ +# define RH_3SKI (1<<14) /* SeekIncomplete (also sets UNS+ATA+PIP+RDY)*/ +# define RH_3OPE (1<<13) /* Drive address plug was removed */ +# define RH_3IVC (1<<12) /* Invalid Command (really drive not valid) */ +# define RH_3LSC (1<<11) /* LSC Sucks */ +# define RH_3LBC (1<<10) /* Loss of Bitcheck (hardware lossage) */ +# define RH_3DVC (1<<7) /* Device Check (generic hardware lossage) */ +# define RH_3SSE (1<<5) /* Skip Sector found (can't happen in 18bit) */ +# define RH_3DPE (1<<3) /* Data Parity Error in controller */ +#endif /* RM03P|RM80P */ + +/* POS: (DR) ECC POSITION. (RH11:022, Rh20:016) */ +/* PAT: (DR) ECC PATTERN. (RH11:023, Rh20:017) */ + +#endif /* ifndef DVRPXX_INCLUDED */ diff --git a/src/dvtm03.c b/src/dvtm03.c new file mode 100644 index 0000000..6bc487d --- /dev/null +++ b/src/dvtm03.c @@ -0,0 +1,2310 @@ +/* DVTM03.C - Emulates TM03 tape controller under RH20 for KL10 +*/ +/* $Id: dvtm03.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dvtm03.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#include "klh10.h" + +#if !KLH10_DEV_TM03 && CENV_SYS_DECOSF + /* Stupid gubbish needed to prevent OSF/1 AXP compiler from + ** halting merely because compiled file is empty! + */ +static int decosfcclossage; +#endif + +#if KLH10_DEV_TM03 /* Moby conditional for entire file */ + +#include +#include +#include /* For malloc */ +#include + +#include "kn10def.h" +#include "kn10dev.h" +#include "dvuba.h" +#include "dvtm03.h" +#include "prmstr.h" + +#if KLH10_DEV_DPTM03 +# include "dpsup.h" /* Using device subproc! */ +# include "dptm03.h" /* Define stuff shared with subproc */ +#else +# include "wfio.h" /* For word-based file i/o */ +# include "vmtape.h" /* For virtual magtape facilities */ +#endif + +#ifdef RCSID + RCSID(dvtm03_c,"$Id: dvtm03.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Some notes on TM03 support: + +From the viewpoint of a RH11 or RH20 controller, the TM02/3 is a "drive"; +however, by itself it's known as a "formatter" which can control up to 8 +"slave" transport units -- sort of like a subcontroller. + +The best documentation on the TM02/3 is the DEC Technical +Manual titled "TM03 Magnetic Tape Formatter", part EK-0TM03-TM-003. +The last known edition was Dec 1983. Having the prints also would +be nice... + +*/ + +#ifndef DVTM_NSUP +# define DVTM_NSUP (8*8) +#endif +#ifndef DVTM_MAXPATH /* Length of pathname for tapefile */ +# define DVTM_MAXPATH 127 +#endif + +#ifndef DVTM_MAXRECSIZ +# ifdef DPTM_MAXRECSIZ +# define DVTM_MAXRECSIZ DPTM_MAXRECSIZ +# else +# define DVTM_MAXRECSIZ (1L<<16) /* 16 bits worth of record length */ +# endif +#endif + +#if 0 +#define DVDEBUG(d) ((d)->tm_dv.dv_debug) +#define DVDBF(d) ((d)->tm_dv.dv_dbf) +#endif + +struct tmdev { + struct device tm_dv; + + /* Drive(formatter)-specific stuff */ + unsigned int tm_reg[040]; /* Formatter registers (16 bits each) */ + int tm_typ; /* Drive type (TM02/TM03 bit in DT) */ + int tm_bit; /* Attention bit */ + int tm_wc; /* I/O current word count */ + vmptr_t tm_vp; /* I/O current word pointer into phys mem */ + int tm_slv; /* Current selected slave (only 0 used) */ + + /* Stuff for slave 0 */ + int tm_styp; /* Slave device type (RH_DTxx value) */ + int tm_sfmt; /* Slave format select */ + int tm_sfpw; /* Frames per word of selected format */ + int tm_sden; /* Slave density select */ + int tm_srew; /* TRUE if slave is rewinding */ + int tm_scmd; /* Command being executed, with GO bit */ + + unsigned char *tm_buff; /* Start ptr into buffer (local or shared) */ + size_t tm_bchs; /* # bytes used in buffer */ + +#if KLH10_DEV_DPTM03 + char *tm_dpname; /* Pathname of executable subproc */ + struct dp_s tm_dp; /* Handle on dev subprocess */ + struct dptm03_s *tm_sdptm; /* Ptr to shared memory segment */ + + int tm_state; +# define TM03_ST_OFF 0 /* Turned off - no subproc */ +# define TM03_ST_READY 1 /* On and ready for command */ +# define TM03_ST_BUSY 2 /* Executing some command */ + /* Note "on" doesn't imply tape mounted! */ + int tm_dpdbg; /* Initial DP debug val */ + char tm_spath[DVTM_MAXPATH+1]; +#else + unsigned char *tm_bufp; /* Handle on malloced buffer */ + struct vmtape tm_vmt; +#endif +}; + +#define TMREG(d,r) ((d)->tm_reg[r]) + +static int ntms = 0; +struct tmdev /* External for easier debug */ + *dvtm03[DVTM_NSUP]; /* Table of pointers, for easier debug */ +static struct tmdev dvtm; /* First one static for easier debug */ + + +/* Handy macros to eliminate some conditionals */ + +#if KLH10_DEV_DPTM03 +# define TM03_FRMS(tm) ((tm)->tm_sdptm->dptm_frms) +# define TM03_ERRS(tm) ((tm)->tm_sdptm->dptm_err) +#else +# define TM03_FRMS(tm) (vmt_framecnt(&(tm)->tm_vmt)) +# define TM03_ERRS(tm) (vmt_errors(&(tm)->tm_vmt)) +#endif + +/* Internal variables and defs */ + +static int tm03_conf(FILE *f, char *s, struct tmdev *tm); + +static int tm03_init(struct device *d, FILE *of); +static int tm03_readin(struct device *d, FILE *of, w10_t, w10_t *, int); +static int tm03_mount(struct device *d, FILE *f, char *path, char *argstr); +static void tm03_powoff(struct device *d); +static void tm03_reset(struct device *d); +static uint32 tm03_rdreg(struct device *d, int reg); +static int tm03_wrreg(struct device *d, int reg, unsigned int val); +#if KLH10_DEV_DPTM03 +static void tm03_run(struct tmdev *tm); +static void tm03_evhsdon(struct device *d, struct dvevent_s *evp); +static void tm03_evhrwak(struct device *d, struct dvevent_s *evp); +#endif + +static void tm_clear(struct tmdev *tm); +static void tm_cmdxct(struct tmdev *tm, int cmd); +static void tm_cmddon(struct tmdev *tm); +static void tm_nxfn(struct tmdev *tm); +static void tm_attn(struct tmdev *tm); +static void tm_ssint(struct tmdev *tm); +static void tm_space(struct tmdev *tm, int revf); +static int tm_io(struct tmdev *tm, int dirf); +static void tm_ssel(struct tmdev *tm), tm_ssta(struct tmdev *tm); +static int tm_filbuf(struct tmdev *tm); +static int tm_flsbuf(struct tmdev *tm, int revf); +static void tm_showbuf(struct tmdev *tm, + unsigned char *ucp, vmptr_t vp, int wc, int revf); + +static unsigned char *wdstofcd(unsigned char *ucp, vmptr_t vp, int wc); +static unsigned char *wdstofic(unsigned char *ucp, vmptr_t vp, int wc); +static void fcdtowds(vmptr_t vp, unsigned char *ucp, int wc); +static void fictowds(vmptr_t vp, unsigned char *ucp, int wc); +static void revfcdtowds(vmptr_t vp, unsigned char *ucp, int wc, int revf); +static void revfictowds(vmptr_t vp, unsigned char *ucp, int wc, int revf); + +/* Configuration Parameters */ + +#define DVTM03_PARAMS \ + prmdef(TMP_DBG, "debug"), /* Initial debug value */\ + prmdef(TMP_FMTR,"fmtr"), /* Formatter type (eg TM03) */\ + prmdef(TMP_TYP, "type"), /* Slave type (eg TU45) */\ + prmdef(TMP_PATH,"path"), /* Initial mount path of file or raw device */\ + prmdef(TMP_SN, "sn"), /* Serial number */\ + prmdef(TMP_DPDBG,"dpdebug"), /* Initial DP debug value */\ + prmdef(TMP_DP, "dppath") /* Device subproc pathname */ + +enum { +# define prmdef(i,s) i + DVTM03_PARAMS +# undef prmdef +}; + +static char *tmprmtab[] = { +# define prmdef(i,s) s + DVTM03_PARAMS +# undef prmdef + , NULL +}; + + +static int partyp(char *cp, int *atyp); /* Local parsing routines */ +static int parfmtr(char *cp, int *afmtr); + +/* TM03_CONF - Parse configuration string and set defaults. +** At this point, device has just been created, but not yet bound +** or initialized. +** NOTE that some strings are dynamically allocated! Someday may want +** to clean them up nicely if config fails or device is uncreated. +*/ +static int +tm03_conf(FILE *f, char *s, struct tmdev *tm) +{ + int i, ret = TRUE; + struct prmstate_s prm; + char buff[200]; + long lval; + + /* First set defaults for all configurable parameters */ + DVDEBUG(tm) = FALSE; + tm->tm_typ = TM_DTTM03; /* Say formatter is TM03 for now */ + tm->tm_styp = TM_DT45; /* Say slave is TU45 for now */ + TMREG(tm, RHR_SN) = (7<<8)|(5<<4)|9; /* Serial Number register */ +#if KLH10_DEV_DPTM03 + tm->tm_dpname = "dptm03"; /* Subproc executable */ + tm->tm_spath[0] = '\0'; /* Nothing mounted yet */ + tm->tm_dpdbg = FALSE; +#endif + + + prm_init(&prm, buff, sizeof(buff), + s, strlen(s), + tmprmtab, sizeof(tmprmtab[0])); + while ((i = prm_next(&prm)) != PRMK_DONE) { + switch (i) { + case PRMK_NONE: + fprintf(f, "Unknown TM03 parameter \"%s\"\n", prm.prm_name); + ret = FALSE; + continue; + case PRMK_AMBI: + fprintf(f, "Ambiguous TM03 parameter \"%s\"\n", prm.prm_name); + ret = FALSE; + continue; + default: /* Handle matches not supported */ + fprintf(f, "Unsupported TM03 parameter \"%s\"\n", prm.prm_name); + ret = FALSE; + continue; + + case TMP_DBG: /* Parse as true/false boolean or number */ + if (!prm.prm_val) /* No arg => default to 1 */ + DVDEBUG(tm) = 1; + else if (!s_tobool(prm.prm_val, &DVDEBUG(tm))) + break; + continue; + + case TMP_TYP: /* Parse as slave type */ + if (!prm.prm_val) + break; + if (!partyp(prm.prm_val, &tm->tm_styp)) + break; + continue; + + case TMP_FMTR: /* Parse as formatter type */ + if (!prm.prm_val) + break; + if (!parfmtr(prm.prm_val, &tm->tm_typ)) + break; + continue; + + case TMP_SN: /* Parse as decimal number */ + if (!prm.prm_val || !s_todnum(prm.prm_val, &lval)) + break; + if (lval < 0) { + fprintf(f, "TM03 SN must be >= 0\n"); + ret = FALSE; + } else + /* Turn last 4 digits into BCD */ + TMREG(tm, RHR_SN) = + (((lval / 1000)%10) << 12) + | (((lval / 100)%10) << 8) + | (((lval / 10)%10) << 4) + | (((lval )%10) ); + continue; + + case TMP_PATH: /* Parse as simple string */ +#if KLH10_DEV_DPTM03 + if (!prm.prm_val) + break; + if (strlen(prm.prm_val) > DVTM_MAXPATH) { + fprintf(f, "TM03 path too long (max %d)\n", DVTM_MAXPATH); + ret = FALSE; + } else + strcpy(tm->tm_spath, prm.prm_val); +#endif + continue; + + case TMP_DPDBG: /* Parse as true/false boolean or number */ +#if KLH10_DEV_DPTM03 + if (!prm.prm_val) /* No arg => default to 1 */ + tm->tm_dpdbg = 1; + else if (!s_tobool(prm.prm_val, &(tm->tm_dpdbg))) + break; +#endif + continue; + + case TMP_DP: /* Parse as simple string */ +#if KLH10_DEV_DPTM03 + if (!prm.prm_val) + break; + tm->tm_dpname = s_dup(prm.prm_val); +#endif + continue; + } + ret = FALSE; + fprintf(f, "TM03 param \"%s\": ", prm.prm_name); + if (prm.prm_val) + fprintf(f, "bad value syntax: \"%s\"\n", prm.prm_val); + else + fprintf(f, "missing value\n"); + } + + /* Param string all done, do followup checks or cleanup */ + + return ret; +} + +static int +partyp(char *cp, int *atyp) +{ + if (s_match(cp, "TU45") == 2) *atyp = TM_DT45; + else if (s_match(cp, "TE16") == 2) *atyp = TM_DT16; + else if (s_match(cp, "TU77") == 2) *atyp = TM_DT77; + else + return FALSE; + + return TRUE; +} + + +/* Parse formatter type +*/ +static int +parfmtr(char *cp, int *afmtr) +{ + if (s_match(cp, "TM03") == 2) *afmtr = TM_DTTM03; + else if (s_match(cp, "TM02") == 2) *afmtr = TM_DTTM02; + else + return FALSE; + + return TRUE; +} + + +struct device * +dvtm03_create(FILE *f, char *s) +{ + register struct tmdev *tm; + + /* Allocate an TM device structure */ + if (ntms >= DVTM_NSUP) { + fprintf(f, "Too many TMs, max: %d\n", DVTM_NSUP); + return NULL; + } + if (ntms == 0) /* Special-case first TM */ + tm = &dvtm; + else { + if (!(tm = (struct tmdev *)malloc(sizeof(struct tmdev)))) { + fprintf(f, "Cannot allocate TM device! (out of memory)\n"); + return NULL; + } + } + dvtm03[ntms++] = tm; + + /* Various initialization stuff */ + memset((char *)tm, 0, sizeof(*tm)); + + iodv_setnull(&tm->tm_dv); /* Set up as null device */ + tm->tm_dv.dv_dflags = DVFL_CTLIO | DVFL_NBA | DVFL_TAPE; + tm->tm_dv.dv_init = tm03_init; + tm->tm_dv.dv_readin = tm03_readin; + tm->tm_dv.dv_reset = tm03_reset; + tm->tm_dv.dv_rdreg = tm03_rdreg; + tm->tm_dv.dv_wrreg = tm03_wrreg; + tm->tm_dv.dv_powoff = tm03_powoff; + tm->tm_dv.dv_mount = tm03_mount; + + /* TM-specific stuff */ + + /* Configure drive from parsed string. + */ + if (!tm03_conf(f, s, tm)) + return NULL; + + return &tm->tm_dv; +} + +static int +tm03_init(struct device *d, FILE *of) +{ + register struct tmdev *tm = (struct tmdev *)d; + + tm->tm_bit = 1 << tm->tm_dv.dv_num; /* Set attention bit mask */ + + /* Set up stuff for slave 0 */ + TMREG(tm, RHR_DT) = TM_DTNS | TM_DTTA /* Not sector, and tape */ + | TM_DTSS /* Slave 0 always there */ + | tm->tm_typ | tm->tm_styp; /* Formatter & slave type */ + tm->tm_sfmt = 0 /* TM_FCD */; /* PDP-10 Core-Dump format */ + tm->tm_sfpw = 5; + tm->tm_sden = 0 /* TM_D02 */; /* 200bpi */ + tm->tm_srew = FALSE; /* Not rewinding */ + tm->tm_scmd = 0; /* No command */ + +#if KLH10_DEV_DPTM03 + { + register struct dptm03_s *dptm; + struct dvevent_s ev; + + tm->tm_state = TM03_ST_OFF; + + if (!dp_init(&tm->tm_dp, sizeof(struct dptm03_s), + DP_XT_MSIG, SIGUSR1, 0, /* in fr dp */ + DP_XT_MSIG, SIGUSR1, (size_t)DPTM_MAXRECSIZ)) { /* out to dp */ + if (of) fprintf(of, "TM03 subproc init failed!\n"); + return FALSE; + } + tm->tm_buff = dp_xsbuff(&(tm->tm_dp.dp_adr->dpc_todp), (size_t *)NULL); + memset(tm->tm_buff - 4, 0, 4); /* Clear prefix padding */ + /* See dptm_revpad and tm_flsbuf */ + + /* Set up TM03-specific part of shared DP memory */ + dptm = (struct dptm03_s *) tm->tm_dp.dp_adr; + tm->tm_sdptm = dptm; + tm->tm_dv.dv_dpp = &(tm->tm_dp); /* Tell CPU where our DP struct is */ + + dptm->dptm_dpc.dpc_debug = tm->tm_dpdbg; /* Init debug flag */ + if (cpu.mm_locked) /* Lock DP mem if CPU is */ + dptm->dptm_dpc.dpc_flags |= DPCF_MEMLOCK; + + dptm->dptm_blkopen = 10; /* Use retry of 10 for now */ + + /* Register ourselves with main KLH10 loop for DP events */ + + ev.dvev_type = DVEV_DPSIG; /* Event = Device Proc signal */ + ev.dvev_arg.eva_int = SIGUSR1; + ev.dvev_arg2.eva_ip = &(tm->tm_dp.dp_adr->dpc_todp.dpx_donflg); + if (!(*tm->tm_dv.dv_evreg)((struct device *)tm, tm03_evhsdon, &ev)) { + if (of) fprintf(of, "TM03 event reg failed!\n"); + return FALSE; + } + + ev.dvev_type = DVEV_DPSIG; /* Event = Device Proc signal */ + ev.dvev_arg.eva_int = SIGUSR1; + ev.dvev_arg2.eva_ip = &(tm->tm_dp.dp_adr->dpc_frdp.dpx_wakflg); + if (!(*tm->tm_dv.dv_evreg)((struct device *)tm, tm03_evhrwak, &ev)) { + if (of) fprintf(of, "TM03 event reg failed!\n"); + return FALSE; + } + + /* Mount hard device here if specified as init arg? */ + if (tm->tm_spath[0]) { + if (!tm03_mount((struct device *)tm, + of, tm->tm_spath, "hard")) { /* Assume hard, R/W */ + if (of) fprintf(of, "TM03 initial mount of \"%s\" failed!\n", + tm->tm_spath); + return FALSE; + } + } + } + +#else + /* Note following buffer allocation includes extra "revpad" bytes + at the start to handle read-reverse transfers; see tm_flsbuf(). + */ + tm->tm_bufp = (unsigned char *)malloc(sizeof(double)+ + DVTM_MAXRECSIZ); + memset(tm->tm_bufp, 0, sizeof(double)); + tm->tm_buff = tm->tm_bufp + sizeof(double); + tm->tm_bchs = 0; + + vmt_init(&(tm->tm_vmt), "TM03"); +#endif + + tm_clear(tm); + + return TRUE; +} + +/* TM03_POWOFF - Handle "power-off" which usually means the KLH10 is +** being shut down. This is important if using a dev subproc! +*/ +static void +tm03_powoff(struct device *d) +{ + register struct tmdev *tm = (struct tmdev *)d; + + /* Later could add stuff to pretend controller/slave is off, but for + ** now it suffices just to clean up. + */ +#if KLH10_DEV_DPTM03 + (*tm->tm_dv.dv_evreg)( /* Flush all event handlers for device */ + (struct device *)tm, + NULL, /* Null handler to flush */ + (struct dvevent_s *)NULL); + dp_term(&(tm->tm_dp), 0); /* Flush all subproc overhead */ + + tm->tm_state = TM03_ST_OFF; + tm->tm_sdptm = NULL; /* Clear pointers no longer meaningful */ + tm->tm_buff = NULL; +#endif +} + +#if KLH10_DEV_DPTM03 + +static int +tm_cmdwait(register struct tmdev *tm, + FILE *f, + register struct dpx_s *dpx, + int secs) +{ + int cnt; + + for (cnt = 0; !dp_xstest(dpx); ++cnt) { + if (cnt > secs) { + return 0; + } else if (cnt & 01) /* Every other sec */ + if (f) fprintf(f, "[TM03 busy, waiting...]\n"); + os_sleep(1); + dev_evcheck(); + } + if (f) fprintf(f, "[TM03 ready]\n"); + return 1; +} + +static int +tm_blkcmd(register struct tmdev *tm, + FILE *f, + int cmd, size_t arg) +{ + register struct dpx_s *dpx = &(tm->tm_dp.dp_adr->dpc_todp); + + if (!tm_cmdwait(tm, f, dpx, 10)) { + if (f) fprintf(f, "[TM03 still busy, giving up before cmd %o]\n", cmd); + return FALSE; /* Barf if not ready in time */ + } + dp_xsend(dpx, cmd, arg); /* Send command! */ + if (!tm_cmdwait(tm, f, dpx, 10)) { + if (f) fprintf(f, "[TM03 still busy, giving up after cmd %o]\n", cmd); + return FALSE; /* Barf if not ready in time */ + } + return TRUE; +} + +#endif /* KLH10_DEV_DPTM03 */ + +/* TM03_READIN - do special readin for boot code +** Requires special hackery as we are bypassing all of the +** normal I/O procedures, which assume an initialized controller. +*/ +static int +tm03_readin(struct device *d, + FILE *f, + w10_t blka, /* Interpreted as file #, 0 = first */ + w10_t *wp, int wc) +{ + register struct tmdev *tm = (struct tmdev *)d; + register size_t frmc = wc * 5; /* Assume core-dump format */ + size_t fskip = W10_U32(blka); + + if (frmc > DVTM_MAXRECSIZ) + frmc = DVTM_MAXRECSIZ; + + /* If DP, may want to try waiting for response at this point. */ + + tm_clear(tm); + if (!(TMREG(tm, RHR_STS) & TM_SMOL)) { /* Ensure medium on-line */ + if (f) fprintf(f, "[tm03_readin: tape off-line]\n"); + return 0; /* Tape not mounted or not ready */ + } + + /* Space forward by given # of files, then read 1 record */ +#if KLH10_DEV_DPTM03 + if (fskip) { + if (f) fprintf(f, "[tm03_readin: skipping %ld]\n", (long)fskip); + if (!tm_blkcmd(tm, f, DPTM_SFF, fskip)) + return 0; + } + if (!tm_blkcmd(tm, f, DPTM_RDF, frmc)) { + return 0; + } +#else + if (fskip && !vmt_fspace(&(tm->tm_vmt), 0, (long)fskip)) { + return 0; + } + (void) vmt_rget(&(tm->tm_vmt), tm->tm_buff, (long)frmc); +#endif + + frmc = TM03_FRMS(tm); /* Get # frames in record */ + wc = frmc / 5; /* Find # whole words */ + if (wc) { + fcdtowds(wp, tm->tm_buff, wc); + } + return wc; +} + +#if KLH10_DEV_DPTM03 + +/* TM03_RUN - Not sure if this one makes sense. +*/ +static void +tm03_run(register struct tmdev *tm) +{ +} + +/* TM_DPCMD - Carry out an asynchronous command +*/ +static void +tm_dpcmd(register struct tmdev *tm, int cmd, size_t arg) +{ + register struct dpx_s *dpx = &(tm->tm_dp.dp_adr->dpc_todp); + + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm_dpcmd: DP %d, %ld]\r\n", cmd, (long)arg); + + /* First, double-check to be sure it's OK to send a command */ + if (tm->tm_state != TM03_ST_READY) { + /* Says not ready -- check DP to see if true */ + if (!(tm->tm_state == TM03_ST_BUSY) || !dp_xstest(dpx)) { + /* Yep, really can't send command now. + ** This shouldn't happen; what to do? + */ + fprintf(DVDBF(tm), + "[TM03 %s internal error: dpcmd %d blocked, state %d]\r\n", + tm->tm_dv.dv_name, cmd, tm->tm_state); + /* Try to keep going by ignoring this command */ + return; + } + /* Hmmm, state is BUSY but dp_xstest thinks we're OK, so go ahead */ + } + tm->tm_state = TM03_ST_BUSY; + + dp_xsend(dpx, cmd, arg); /* Send command! */ +} + + +/* TM03_EVHSDON - Invoked by INSBRK event handling when +** signal detected from DP saying "done" in response to something +** we sent it. +** Basically this means the DP should be ready to accept another +** command. +*/ +static void +tm03_evhsdon(struct device *d, + register struct dvevent_s *evp) +{ + register struct tmdev *tm = (struct tmdev *)d; + + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm03_evhsdon: %d]", + (int)dp_xstest(&(tm->tm_dp.dp_adr->dpc_todp))); + + tm->tm_state = TM03_ST_READY; /* Say ready for cmd again */ + tm_cmddon(tm); +} + +/* TM03_EVHRWAK - Invoked by INSBRK event handling when +** signal detected from DP saying "wake up"; the DP is sending +** us something. +** The TM03 will use this to receive notice of unexpected manual events, +** specifically tape being mounted or unmounted. +*/ +static void +tm03_evhrwak(struct device *d, + register struct dvevent_s *evp) +{ + register struct tmdev *tm = (struct tmdev *)d; + register struct dpx_s *dpx = &(tm->tm_dp.dp_adr->dpc_frdp); + + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm03_evhrwak: %d]", (int)dp_xrtest(dpx)); + + if (dp_xrtest(dpx)) { /* Verify there's a message for us */ + switch (dp_xrcmd(dpx)) { + case DPTM_MOUNT: + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm03_evhrwak: Tape Online!]\r\n"); + if (tm->tm_slv == 0) { /* If still right slave */ + tm_ssta(tm); /* update all status */ + } + TMREG(tm, RHR_STS) |= TM_SSSC; /* Set Slave Status Change */ + tm_attn(tm); + break; + + default: + break; + } + dp_xrdone(dpx); /* just ACK it */ + } +} + + +static int +tm03_start(register struct tmdev *tm) +{ + if (tm->tm_state != TM03_ST_OFF) { + fprintf(DVDBF(tm), "[tm03_start: Already running?]\r\n"); + return FALSE; + } + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm03_start: Starting DP \"%s\"...", + tm->tm_dpname); + if (!dp_start(&tm->tm_dp, tm->tm_dpname)) { + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), " failed!]\r\n"); + else + fprintf(DVDBF(tm), "[tm03_start: Start of DP \"%s\" failed!]\r\n", + tm->tm_dpname); + return FALSE; + } + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), " started!]\r\n"); + + tm->tm_state = TM03_ST_READY; + return TRUE; +} + +#endif /* KLH10_DEV_DPTM03 */ + +/* TM03_MOUNT - Mount or dismount a tape. +** If path is NULL, wants to dismount; argstr is ignored. +** If path is "", just wants status report. +** Else mounting tape; argstr if present has keyword params which +** are parsed by vmt_attrparse(), e.g.: +** "hard", "8mm", etc - indicate hardware device or type +** "ro" - Read-Only +** "rw" - Create then Read/Write (default) +** Returns: +** 0 - error. Error message already output to stream, if one. +** 1 - action succeeded. +*/ +static int +tm03_mount(struct device *d, FILE *f, char *path, char *argstr) +{ + register struct tmdev *tm = (struct tmdev *)d; + + int err = FALSE; + char *opath; + +#if KLH10_DEV_DPTM03 + register size_t cnt; + + opath = tm->tm_spath[0] ? tm->tm_spath : NULL; +#else + int prevmount = vmt_ismounted(&(tm->tm_vmt)); /* Get state */ + opath = (prevmount ? vmt_tapepath(&(tm->tm_vmt)) : NULL); +#endif + + if (path && !*path) { + /* Just wants mount status report */ + if (!f) /* If no output stream, can't report */ + return TRUE; + if (!opath) { + fprintf(f, "No tape mounted.\n"); + return TRUE; + } + fprintf(f, "Current tape pathname is \"%s\", status", opath); + +#if KLH10_DEV_DPTM03 + switch (tm->tm_state) { + case TM03_ST_OFF: + fprintf(f, " OFF\n"); + return TRUE; + case TM03_ST_BUSY: + fprintf(f, " BUSY"); + break; + case TM03_ST_READY: + fprintf(f, " READY"); + break; + default: + fprintf(f, " <\?\?%d\?\?>", tm->tm_state); + break; + } + if (tm->tm_sdptm->dptm_mol) + fprintf(f, " ONLINE"); + if (tm->tm_sdptm->dptm_wrl) + fprintf(f, " WRITELOCKED"); +#else + if (prevmount) { + fprintf(f, " ONLINE"); + if (!vmt_iswritable(&(tm->tm_vmt))) + fprintf(f, " WRITELOCKED"); + } else + fprintf(f, " OFFLINE"); +#endif + fprintf(f, "\n"); + return TRUE; + } + + /* Unmount any existing tape, and mount new tape if one provided */ + +#if KLH10_DEV_DPTM03 + /* Should this kill the subproc, or wait its turn to send a command? + ** Don't want to hang waiting for rewind to complete! + */ + /* For now, return error if busy (sigh) + */ + if (tm->tm_state == TM03_ST_BUSY) { + fprintf(f, "Cannot %smount: slave busy\n", (path ? "" : "un")); + return FALSE; + } + if (tm->tm_state == TM03_ST_OFF) { + /* Subproc not running. If call is just unmounting, that's all, + ** else must start it up so it can handle the mount. + */ + if (!path) { + tm->tm_spath[0] = '\0'; /* Make sure no current tapefile */ + fprintf(f, "No tape mounted.\n"); + return TRUE; /* OK, no tape mounted */ + } + + if (!tm03_start(tm)) /* Fire up the subproc! */ + return FALSE; + } + + /* At this point, state should be READY... */ + if (!path) { /* Just unmounting current tape? */ + cnt = 0; /* Tell DP to unmount */ + tm->tm_sdptm->dptm_pathx = 0; + tm->tm_sdptm->dptm_argsx = 0; + tm->tm_buff[0] = '\0'; + } else { + register unsigned char *cp = tm->tm_buff; + register size_t acnt; + + cnt = strlen(path); + if (cnt > DVTM_MAXPATH-1) + cnt = DVTM_MAXPATH-1; + memcpy(tm->tm_spath, path, cnt); /* Remember pathname */ + tm->tm_spath[cnt] = '\0'; + + acnt = argstr ? strlen(argstr) : 0; + if ((1+cnt+1+acnt+1) > DVTM_MAXRECSIZ) { /* Buff overflow chk */ + fprintf(f, "Mount path & args too long! %ld?\n", + (long)DVTM_MAXRECSIZ); + return FALSE; + } + + /* Copy path and args into DP comm buffer, including terminators */ + *cp++ = '\0'; + memcpy(cp, path, cnt); + cp += cnt; + *cp++ = '\0'; + if (acnt) { + memcpy(cp, argstr, acnt); + } + cp[acnt] = '\0'; + tm->tm_sdptm->dptm_pathx = 1; + tm->tm_sdptm->dptm_argsx = 1+cnt+1; + cnt = 1+cnt+1+acnt+1; + } + + /* Do command! And hope for the best... */ + if (!path) + fprintf(f, "Unmount requested\n"); + else + fprintf(f, "Mount requested: \"%s\"\n", path); + tm->tm_scmd = TM_NOP; /* Conspire with tm_cmddon */ + tm_dpcmd(tm, DPTM_MOUNT, (size_t)cnt); + + return TRUE; + +#else /* !KLH10_DEV_DPTM03 */ + { + int res; + + if (!path || !*path) { + /* Wants unmount, args ignored */ + res = vmt_unmount(&tm->tm_vmt); + } else { + res = vmt_pathmount(&tm->tm_vmt, path, argstr); + } + + tm_clear(tm); /* Clear slave 0 status */ + if (!res || !vmt_ismounted(&(tm->tm_vmt))) { + if (prevmount) /* Check - tape previously mounted? */ + tm_ssint(tm); /* Yes, say slave status changed */ + + fprintf(f, "No tape mounted.\n"); + return (!path ? TRUE : FALSE); /* OK if dismounting, else failed */ + } + fprintf(f, "Mount succeeded.\n"); + + /* New tape mounted, set up regs appropriately */ + tm_ssta(tm); /* Set up regs from VMT state */ + TMREG(tm, RHR_STS) |= TM_SSLA; /* Pretend slave just came online */ + tm_ssint(tm); /* Say slave status changed */ + return TRUE; + } +#endif /* !KLH10_DEV_DPTM03 */ +} + + +/* TM03_RESET - clear formatter and selected slave +*/ +static void +tm03_reset(struct device *d) +{ + register struct tmdev *tm = (struct tmdev *)d; + + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm03_reset]"); + tm_clear(tm); +} + +/* TM_CLEAR - clear formatter and selected slave +*/ +static void +tm_clear(register struct tmdev *tm) +{ + /* Turn off any attention bit. */ + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm_clear: attn off]"); + (*tm->tm_dv.dv_attn)(&tm->tm_dv, 0); + + /* Clear and set Drive bits in CS1 */ + TMREG(tm, RHR_CSR) = TM_1DA; /* Drive/formatter Available */ + TMREG(tm, RHR_MNT) &= ~(1<<6); /* R/W [-2] MNT Maintenance */ + /* Clears all but bit 6 */ + + /* Clearing errors may be tricky. Have to ensure that slave + ** errors are reset as well -- if this involves a command to + ** the DP then how to wait for synchronization to happen? + ** May need to have a shared "clear-before-executing-cmd" flag which can + ** be set anytime. + */ +#if KLH10_DEV_DPTM03 + if (tm->tm_sdptm) { /* Check in case DP startup failed */ + tm->tm_sdptm->dptm_err = 0; /* So tm_ssta doesn't spill beans */ + tm->tm_sdptm->dptm_col = 0; /* So TM_SSLA gets turned off! */ + } +#endif + TMREG(tm, RHR_ER1) = 0; /* R/W ER1 Error 1 */ + TMREG(tm, RHR_STS) = /* RO FS Formatter Status */ + TM_SDPR; /* Drive/formatter Present */ + tm_ssta(tm); /* Set status bits per selected slave */ + +#if 0 /* Clear doesn't touch these */ + TMREG(tm, RHR_BAFC) = 0; /* R/W [I2] ADR Block Address or Frame Count */ + TMREG(tm, RHR_DT) = /* RO [I2] TYP Drive Type */ + TM_DTNS|TM_DTTA|TM_DTSS|tm->tm_typ|tm->tm_styp; + TMREG(tm, RHR_LAH) = 0; /* RO LAH Current BlkAdr or R/W ChkChr */ + TMREG(tm, RHR_OFTC) = 0; /* R/W OFS Offset or TapeControl */ + TMREG(tm, RHR_SN) = 15414; /* RO [-2] SER Serial Number */ +#endif +} + + +static uint32 +tm03_rdreg(struct device *d, int reg) +{ + register struct tmdev *tm = (struct tmdev *)d; + + switch (reg) { + + /* In general, device registers are just read directly. + ** Note that the TM02/TM03 doesn't implement all RH20 registers; + ** attempting to reference unknown regs will fail with an ILR error. + */ + case RHR_CSR: /* R/W CS1 Control/command */ + case RHR_STS: /* RO [I2] STS Status */ + case RHR_ER1: /* R/W ER1 Error 1 */ + case RHR_MNT: /* R/W [-2] MNT Maintenance */ + case RHR_ATTN: /* R/W [I2] ATN Attention Summary */ + case RHR_BAFC: /* R/W [I2] ADR Block Address or Frame Count */ + case RHR_DT: /* RO [I2] TYP Drive Type */ + case RHR_LAH: /* RO LAH Current BlkAdr or R/W ChkChr */ + case RHR_SN: /* RO [-2] SER Serial Number */ + + case RHR_OFTC: /* R/W OFS Offset or TapeControl */ + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm03_rdreg: r%o/ %o]\r\n", + reg, TMREG(tm, reg)); + return TMREG(tm, reg); + + default: /* Unknown register */ + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm03_rdreg: unknown reg %o]\r\n", reg); + break; /* Return error, caller will handle */ + } + + /* If illegal register was selected, sets ILR bit in error reg, but + ** doesn't generate ATTN. + */ + TMREG(tm, RHR_ER1) |= TM_EILR; /* Set ILR error bit */ + TMREG(tm, RHR_STS) |= TM_SERR; /* And composite error */ + + return -1; /* Return error, caller will handle */ +} + +static int +tm03_wrreg(struct device *d, + register int reg, + register dvureg_t val) +{ + register struct tmdev *tm = (struct tmdev *)d; + register int gobit; + + val &= MASK16; + + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm03_wrreg: r%o/ %o = %o]\r\n", + reg, TMREG(tm, reg), val); + + + /* If GO bit is still set, all reg mods are refused + ** except for the ATTN and MNT registers. + */ + gobit = TMREG(tm, RHR_CSR) & TM_1GO; + + switch (reg) { + + case RHR_CSR: /* R/W CS1 Control/command */ + if (gobit) /* If formatter is busy, */ + break; /* Refuse register modification! */ + + /* Set any permissible drive bits */ + TMREG(tm, RHR_CSR) &= ~(TM_1CM); /* Bits can set */ + TMREG(tm, RHR_CSR) |= (val & TM_1CM); /* Set em */ + val &= TM_1CM; + if (val & TM_1GO) { + tm_cmdxct(tm, val); /* Perform drive command */ + } + return 1; + + case RHR_ATTN: /* R/W [I2] ATN? Attention Summary */ + /* This register is actually intercepted and handled specially + ** by the controller. At this point, only this specific drive + ** is being addressed, so only one bit of information + ** is meaningful; consider the entire value to be either 0 or non-0. + ** If non-zero, turns off the ATTN bit for this drive/formatter. + */ + /* Ignores state of GO bit */ + if (val) { /* Any live bits set? */ + TMREG(tm, RHR_ATTN) = 0; /* Yep, turn them off! */ + TMREG(tm, RHR_STS) &= ~TM_SATA; /* Clear status bit */ + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm_attn: off]"); + (*tm->tm_dv.dv_attn)(&tm->tm_dv, 0); /* Tell controller it's off */ + } + return 1; + + case RHR_MNT: /* R/W [-2] MNT Maintenance */ + /* Ignores state of GO bit */ + TMREG(tm, reg) = val; /* Copy value but do nothing */ + return 1; + + /* Frame Count register. Note TM03 clears this automatically for + ** a read operation. + */ + case RHR_BAFC: /* R/W [I2] ADR Block Address or Frame Count */ + if (gobit) + break; + TMREG(tm, reg) = val; /* Set reg */ + TMREG(tm, RHR_OFTC) |= TM_TFCS; /* Set Frame Count Status bit */ + return 1; + + /* Read-Only registers, write is no-op */ + case RHR_STS: /* RO [I2] STS Status */ + case RHR_ER1: /* RO ER1 Error 1 */ + case RHR_DT: /* RO [I2] TYP Drive Type */ + case RHR_LAH: /* RO Current BlockAddr or R/W CheckChar */ + case RHR_SN: /* RO [-2] SER Serial Number */ + if (gobit) + break; + return 1; + + /* Special - Tape Control register effects slave selection! */ + case RHR_OFTC: /* R/W OFS Offset or TapeControl */ + if (gobit) + break; + + /* Not clear which bits are RO; TM03 doc only identifies + ** ACCL as explicitly RO. For now, treat FCS as RO also. + ** Don't bother setting SAC (Slave Address Change) as it probably + ** gets turned off almost immediately by everything. + ** Treat it as RO also. + */ +# define TMTCBITS (TM_TEA|TM_TDS|TM_TFS|TM_TTS) /* R/W bits */ + + TMREG(tm,RHR_OFTC) = (TMREG(tm,RHR_OFTC) & ~TMTCBITS) + | (val & TMTCBITS); + + if (tm->tm_slv != (val & TM_TTS)) { + /* Slave selection changed! + ** For now, only slave 0 supported, which simplifies code. + */ + tm_ssel(tm); /* Effect slave selection */ + } + + /* Only hack tape config params for valid slave */ + if (tm->tm_slv == 0) { + int oden, ofmt; + + oden = tm->tm_sden; /* Remember old values */ + ofmt = tm->tm_sfmt; + tm->tm_sden = (val & TM_TDS)>>8; /* Get new density */ + tm->tm_sfmt = (val & TM_TFS)>>4; /* and format */ + + if ((oden != tm->tm_sden) || (ofmt != tm->tm_sfmt)) { + /* Something changed, so effect it */ + /* Set format (data mode) */ + switch (tm->tm_sfmt) { + case TM_FCD: tm->tm_sfpw = 5; break; + case TM_FIC: tm->tm_sfpw = 4; break; + default: + fprintf(DVDBF(tm), + "[tm03_wrreg: Unsupported data mode %d]\r\n", + tm->tm_sfmt); + tm->tm_sfmt = TM_FCD; /* Default to core-dump */ + tm->tm_sfpw = 5; + } + } + } + return 1; + + default: /* Unknown register */ + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm03_wrreg: unknown reg %o]\r\n", reg); + + /* If illegal register was selected, sets ILR bit in error reg, but + ** doesn't generate ATTN. + */ + TMREG(tm, RHR_ER1) |= TM_EILR; /* Set ILR error bit */ + TMREG(tm, RHR_STS) |= TM_SERR; /* And composite error */ + return 0; /* Return error, caller will handle */ + } + + /* Comes here to set RMR error bit (register modif refused). + ** As for ILR, doesn't generate ATTN. + */ + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm03_wrreg: reg mod refused: %o]\r\n", reg); + + TMREG(tm, RHR_ER1) |= TM_ERMR; /* Set RMR error bit */ + TMREG(tm, RHR_STS) |= TM_SERR; /* And composite error */ + return 1; +} + + +/* TM_SSEL - Do whatever is needed to effect selection of new slave transport. +*/ +static void +tm_ssel(register struct tmdev *tm) +{ + tm->tm_slv = (TMREG(tm, RHR_OFTC) & TM_TTS); + + TMREG(tm, RHR_DT) &= ~(TM_DTSS /* Turn off Slave-Present */ + | TM_DTDT); /* and drive type */ + + if (tm->tm_slv == 0) { + /* Set up register values for slave 0 */ + TMREG(tm, RHR_DT) |= TM_DTSS /* Set Slave-Present */ + | tm->tm_typ | tm->tm_styp; /* and type */ + } else { + /* Set up register values for non-existent slave */ + TMREG(tm, RHR_DT) |= /* Set type to "none" */ + tm->tm_typ | TM_DT00; /* plus TM02/3 bit */ + } + tm_ssta(tm); /* Set status register bits */ +} + +/* TM_SSTA - Set Status bits from slave info +*/ +static void +tm_ssta(register struct tmdev *tm) +{ + register unsigned int sts = TMREG(tm, RHR_STS); + + sts &= ~(TM_SPIP|TM_SMOL|TM_SWRL|TM_SEOT /* Clear bits we'll check */ + |TM_STM|TM_SDRY|TM_SPES|TM_SDWN|TM_SBOT|TM_SSLA); + + if (tm->tm_slv != 0) { + TMREG(tm, RHR_STS) = sts; /* Set new value of status register! */ + return; /* Non-ex slave selected, no bits to set */ + } + +#if KLH10_DEV_DPTM03 + + { + register struct dptm03_s *dptm = tm->tm_sdptm; + + if (tm->tm_state != TM03_ST_OFF && dptm) { + /* Slave present, see if ready for commands */ + if (tm->tm_state == TM03_ST_READY || tm->tm_srew) + sts |= TM_SDRY; /* Slave present & ready for commands */ + + /* SLA is a little peculiar as it is not a static state like MOL; it + is set only when the slave comes online (MOL->1) while selected by + the TM03. It is not turned on just by being selected while MOL=1. + Cleared by TM03 init, drive clear, and if drive goes off-line. + */ + if (dptm->dptm_col) sts |= TM_SSLA; /* Slave Attn (came online) */ + + if (dptm->dptm_pip) sts |= TM_SPIP; /* Positioning in Progress */ + if (dptm->dptm_mol) sts |= TM_SMOL; /* Medium online */ + if (dptm->dptm_wrl) sts |= TM_SWRL; /* Write-locked */ + + if (dptm->dptm_bot) sts |= TM_SBOT; /* Physical BOT */ + if (dptm->dptm_eot) sts |= TM_SEOT; /* Physical EOT */ + if (dptm->dptm_eof) sts |= TM_STM; /* At TapeMark (EOF) */ + } + } +#else +# if 0 + sts |= TM_SSLA; /* Slave Attention (came online) */ +# endif + + sts |= TM_SDRY; /* Assume "slave" always there */ + + if (vmt_ismounted(&(tm->tm_vmt))) sts |= TM_SMOL; /* Medium online */ + if (vmt_isatbot(&(tm->tm_vmt))) sts |= TM_SBOT; /* Phys BOT */ + if (vmt_isateot(&(tm->tm_vmt))) sts |= TM_SEOT; /* Phys EOT */ + if (vmt_isateof(&(tm->tm_vmt))) sts |= TM_STM; /* Tapemark (EOF) */ + if (!vmt_iswritable(&(tm->tm_vmt))) sts |= TM_SWRL; /* Write-locked */ +#endif + + TMREG(tm, RHR_STS) = sts; /* Set new value of status register! */ +} + +/* Execute TM02/3 command. +** Note that tm_cmdxct cannot be called unless the GO bit is off! +** +** There are a variety of funny cases with respect to what commands can +** be executed when. +** One of the most significant is rewinding, because rewind is the +** only operation that slaves can perform independently of the TM03; that +** is, unselected slaves can continue to rewind while the TM03 pays attention +** to the selected slave. +** A command can be given to a rewinding slave if DRY (drive ready) is +** true. However, it will sit in the CSR and not actually be executed until +** the rewind is complete. The tm_srew flag exists to help support this +** behavior. +*/ +static void +tm_cmdxct(register struct tmdev *tm, int cmd) +{ + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm_cmdxct: %#o]\r\n", cmd); + + /* The TM03 doc (p. 4-43) claims that giving a command with GO + ** while an error condition exists will always fail (the operation + ** is "inhibited"). However, if this were literally true, there + ** would be NO WAY to clear the error conditions! + ** Thus, I'm assuming that TM_CLR is specially recognized regardless + ** of whether any errors exist or not. + */ + + /* Note that only TM_CLR can be executed regardless of whether a + ** valid slave is selected or not. + ** TM_RIP requires that slave 0 be valid. + ** All commands but TM_CLR also require that the selected slave have + ** its MOL status bit set. + ** I/O xfer operations to an invalid slave must be aborted specially + ** so the channel can be stopped and cleaned up. + */ + + /* Check for always-legal CLR command */ + if (cmd == TM_CLR) { + tm_clear(tm); + return; + } + + /* Now check for pre-existing error */ + if (TMREG(tm, RHR_STS) & TM_SERR) { /* Check composite bit */ + TMREG(tm, RHR_CSR) &= ~TM_1GO; /* Turn off GO */ + tm_attn(tm); /* and set ATA to interrupt */ + return; + } + + /* Now check for being in rewind state - if so, command must be left + ** in CSR until rewind is done or something else (eg CLR) happens. + */ + if (tm->tm_srew) { + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[TM03 rewinding, cmd deferred]\r\n"); + return; + } + + /* Check for commands that are legal regardless of selected slave. + ** I'm not entirely certain about these but they seem sensible. + */ + switch (cmd) { + case TM_NOP: /* No Operation */ + TMREG(tm, RHR_CSR) &= ~TM_1GO; /* Turn off GO */ + return; /* NOP is not defined as setting ATA when done */ + + case TM_RIP: /* Read-In Preset (not used by T20 or ITS) */ + /* Set tape control reg to slave 0, odd parity, PDP-10 coredump + ** fmt, and 800bpi NRZI + */ + TMREG(tm, RHR_OFTC) &= ~(TM_TDS|TM_TFS|TM_TEP|TM_TTS); + TMREG(tm, RHR_OFTC) |= (TM_D08<<8) | (TM_FCD<<4); + tm_ssel(tm); /* Effect the slave 0 selection */ + + /* Now attempt to start rewind of slave 0 */ + cmd = TM_REW; /* Turn current command into "Rewind"! */ + break; + +#if 0 /* CLR is now handled by test prior to composite-error check */ + case TM_CLR: /* Formatter clear (reset errors etc.) */ + tm_clear(tm); + return; +#endif + } + + /* Check for valid slave currently selected and online. */ + if (!(TMREG(tm, RHR_STS) & TM_SMOL)) { /* No "Medium Online"? */ + /* Must distinguish between I/O commands and everything else. + ** I/O xfers must invoke special handler. + */ + if ((061 <= cmd && cmd <= 067) /* Write function? */ + || (071 <= cmd && cmd <= 077)) { /* Read function? */ + (*tm->tm_dv.dv_iobeg)(&tm->tm_dv, (cmd < 070)); /* Set up xfer */ + (*tm->tm_dv.dv_drerr)(&tm->tm_dv); /* then say error */ + } + /* Always add error bit for "Unsafe" -- can't find any + ** other plausible bit for non-existent slave. + */ + TMREG(tm, RHR_CSR) &= ~TM_1GO; /* Turn off GO */ + TMREG(tm, RHR_ER1) |= TM_EUNS; /* Unsafe */ + TMREG(tm, RHR_STS) |= TM_SERR; /* Error summary */ + tm_attn(tm); /* Send attention interrupt */ + return; + } + + /* Now do all other functions - slave known to exist. + ** At this point, commands CLR, NOP, and RIP have already been handled. + */ + tm->tm_scmd = cmd; /* Remember last cmd executed */ + switch (cmd) { + + case TM_UNL: /* Unload */ +#if KLH10_DEV_DPTM03 + tm->tm_srew = TRUE; /* Now rewinding */ + tm->tm_sdptm->dptm_mol = FALSE; /* Say slave went offline */ + TMREG(tm, RHR_STS) &= ~TM_SMOL; /* Turn off corresponding status bit */ + TMREG(tm, RHR_STS) |= TM_SSSC; /* Slave Status Change (went offline)*/ + TMREG(tm, RHR_STS) |= TM_SPIP; /* Positioning in progress */ + tm_dpcmd(tm, DPTM_UNL, (size_t)0); /* Send UNLOAD command to DP */ +#else + /* Close, dismount */ + /* stdout is not entirely right, but fix up tm03_mount later */ + tm03_mount((struct device *)tm, stdout, (char *)NULL, NULL); +#endif + break; /* Turn off GO and send attention interrupt */ + + case TM_REW: /* Rewind */ +#if KLH10_DEV_DPTM03 + tm->tm_srew = TRUE; /* Now rewinding */ + TMREG(tm, RHR_STS) |= TM_SPIP; /* Positioning in progress */ + tm_dpcmd(tm, DPTM_REW, (size_t)0); /* Send REWIND command to DP */ + break; /* Turn off GO, then signal attn */ +#else + vmt_rewind(&tm->tm_vmt); + tm_cmddon(tm); /* Say rewind completed, turn off GO, signal ATTN */ + return; +#endif + + case TM_ER3: /* Erase three inch gap */ +#if KLH10_DEV_DPTM03 + TMREG(tm, RHR_STS) &= ~TM_SDRY; /* Drive busy, note GO still on! */ + tm_dpcmd(tm, DPTM_ER3, (size_t)0); /* Send ERASE-3 command to DP */ + return; /* Don't signal attn til done */ +#else + break; /* No-op for now, just signal attention */ +#endif + + case TM_WTM: /* Write Tape Mark */ + if (TMREG(tm, RHR_STS) & TM_SWRL) { /* Is it write-locked? */ + TMREG(tm, RHR_CSR) &= ~TM_1GO; /* Turn off GO */ + tm_nxfn(tm); /* Say non-executable function error */ + return; + } +#if KLH10_DEV_DPTM03 + TMREG(tm, RHR_STS) &= ~TM_SDRY; /* Drive busy, note GO still on! */ + tm_dpcmd(tm, DPTM_WTM, (size_t)0); /* Send WTM command to DP */ + return; /* Don't signal attn til done */ +#else + if (!vmt_eof(&tm->tm_vmt)) { + /* Either malloc failed or format isn't raw. */ + fprintf(DVDBF(tm), + "[TM03: vmt_eof failed, EOF not written]\r\n"); + TMREG(tm, RHR_CSR) &= ~TM_1GO; /* Turn off GO */ + tm_nxfn(tm); + return; + } + break; /* Won, signal attn */ +#endif + + case TM_SPF: /* Space Forward records or tapemark */ + tm_space(tm, 0); +#if !KLH10_DEV_DPTM03 + tm_cmddon(tm); /* Do post-xct stuff, includes clearing GO */ +#endif + return; + + case TM_SPR: /* Space Reverse records or tapemark */ + tm_space(tm, 1); +#if !KLH10_DEV_DPTM03 + tm_cmddon(tm); /* Do post-xct stuff, includes clearing GO */ +#endif + return; + + /* The remaining commands are all I/O xfer commands */ + + case TM_WRT: /* Write Forward */ + if (TMREG(tm, RHR_STS) & TM_SWRL) { /* Is it write-locked? */ + TMREG(tm, RHR_CSR) &= ~TM_1GO; /* Turn off GO */ + tm_nxfn(tm); /* Give error "Non-Executable Function" */ + return; + } + /* Writes must ensure that frame count reg was set; FCS bit tells + ** us whether a valid count exists. + */ + if (!(TMREG(tm, RHR_OFTC)&TM_TFCS)) { + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), + "[tm_cmdxct: NEF - write when FCS=0]\r\n"); + TMREG(tm, RHR_CSR) &= ~TM_1GO; /* Turn off GO */ + tm_nxfn(tm); /* Give error "Non-Executable Function" */ + return; + } +#if KLH10_DEV_DPTM03 + if (!tm_io(tm, 0)) { /* Errors handled by tm_io now */ + TMREG(tm, RHR_CSR) &= ~TM_1GO; /* Turn off GO */ + } + TMREG(tm, RHR_STS) &= ~TM_SDRY; /* Drive busy, note GO still on! */ +#else + (void) tm_io(tm, 0); /* Errors handled by tm_io now */ + tm_cmddon(tm); /* Do post-xct stuff, includes clearing GO */ +#endif + return; /* IO operations don't trigger ATTN when done */ + + case TM_WCF: /* Write Check Forward (same as Read Forward) */ + case TM_RDF: /* Read Forward */ + /* CROCK ALERT! If operation is reading, formatter clears BAFC + ** at the start of the transfer, so at the end it contains the number + ** of frames read! + ** 0 happens to have the same meaning as "max count" so this reset + ** will never impose a limit on the size of the transfer; that's up to + ** the controller. + */ + TMREG(tm, RHR_BAFC) = 0; /* Reading, force count 0 */ + TMREG(tm, RHR_OFTC) &= ~TM_TFCS; /* Clear FCS bit */ + +#if KLH10_DEV_DPTM03 + if (!tm_io(tm, 1)) { /* Errors handled by tm_io now */ + TMREG(tm, RHR_CSR) &= ~TM_1GO; /* Turn off GO */ + } + TMREG(tm, RHR_STS) &= ~TM_SDRY; /* Drive busy, note GO still on! */ +#else + (void) tm_io(tm, 1); /* Errors handled by tm_io now */ + tm_cmddon(tm); /* Do post-xct stuff, includes clearing GO */ +#endif + return; /* IO operations don't trigger ATTN when done */ + + case TM_WCR: /* Write Check Reverse (same as Read Reverse) */ + case TM_RDR: /* Read Data Reverse (not used by ITS) */ + /* Note that T20 may want to use this, argh! */ + TMREG(tm, RHR_BAFC) = 0; /* Reading, force count 0 */ + TMREG(tm, RHR_OFTC) &= ~TM_TFCS; /* Clear FCS bit */ + +#if KLH10_DEV_DPTM03 + if (!tm_io(tm, -1)) { /* Errors handled by tm_io now */ + TMREG(tm, RHR_CSR) &= ~TM_1GO; /* Turn off GO */ + } + TMREG(tm, RHR_STS) &= ~TM_SDRY; /* Drive busy, note GO still on! */ +#else + (void) tm_io(tm, -1); /* Errors handled by tm_io now */ + tm_cmddon(tm); /* Do post-xct stuff, includes clearing GO */ +#endif + return; + + + default: + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[TM03 unknown cmd %#o]\r\n", cmd); + + TMREG(tm, RHR_CSR) &= ~TM_1GO; /* Turn off GO */ + TMREG(tm, RHR_ER1) |= TM_EILF; /* Illegal Function code */ + TMREG(tm, RHR_STS) |= TM_SERR; /* Error summary */ + tm_attn(tm); /* Send attention interrupt */ + return; + } + + /* Command done and wants to set attention bit */ + TMREG(tm, RHR_CSR) &= ~TM_1GO; /* Turn off GO */ + tm_attn(tm); +} + + +/* TM_CMDDON - Command/operation completed, wrap it up. +** Slave is known to be quiescent at this point. +*/ +static void +tm_cmddon(register struct tmdev *tm) +{ + register int cmd = tm->tm_scmd; /* Find command to complete */ + + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm_cmddon: %#o]\r\n", cmd); + + tm->tm_srew = FALSE; /* Ensure this is flushed */ + + switch (cmd) { + + case TM_NOP: /* No Operation - actually DPTM_MOUNT! */ + /* This should only happen when a DPTM_MOUNT has completed, + ** because nothing else puts a NOP in tm_scmd. Hack. + ** See also tm03_evhrwak. + */ + if (tm->tm_slv == 0) { /* If still right slave */ + tm_ssta(tm); /* update all status */ + } + if (1) { + /* Horrible crock to give feedback on mount/dismount requests */ + fprintf(DVDBF(tm), "[%s: Tape %s]\r\n", + tm->tm_dv.dv_name, + (TMREG(tm, RHR_STS) & TM_SMOL) ? "online" : "offline"); + } + TMREG(tm, RHR_STS) |= TM_SSSC; /* Set SSC - slave changed state */ + tm_attn(tm); + break; + + case TM_UNL: /* Unload */ + /* Note must test for correct slave since unlike most other + ** commands, regs can be written and thus selection can be changed + ** during UNLOAD or REWIND ops! + */ + if (tm->tm_slv == 0) /* If still right slave */ + TMREG(tm, RHR_STS) &= ~TM_SPIP; /* say no Pos-in-Progress */ + break; + + case TM_REW: /* Rewind */ + /* Same check as for UNLOAD above, for same reason */ + if (tm->tm_slv == 0) { /* If still right slave */ + tm_ssta(tm); /* update all status */ + } + TMREG(tm, RHR_STS) |= TM_SSSC; /* Rew completed, set SSC */ + tm_attn(tm); + break; + + case TM_CLR: /* Formatter clear (reset errors etc.) */ + case TM_RIP: /* Read-In Preset (not used by T20 or ITS) */ + break; + + case TM_ER3: /* Erase three inch gap */ + tm_attn(tm); /* Done, just signal attention */ + break; + + case TM_WTM: /* Write Tape Mark */ + tm_ssta(tm); /* Update all status */ + if (TM03_ERRS(tm)) { + /* Set some kind of error bit here? */ + TMREG(tm, RHR_ER1) |= TM_EOPI; /* What else to use? */ + TMREG(tm, RHR_STS) |= TM_SERR; + } + tm_attn(tm); /* Done, just signal attention */ + break; + + case TM_SPF: /* Space Forward records or tapemark */ + case TM_SPR: /* Space Reverse records or tapemark */ + /* Update BAFC with result */ + TMREG(tm, RHR_BAFC) = + (TMREG(tm, RHR_BAFC) + TM03_FRMS(tm)) & MASK16; + if (TMREG(tm, RHR_BAFC) == 0) /* If counted out, */ + TMREG(tm, RHR_OFTC) &= ~TM_TFCS; /* clear FCS */ + +#if 0 /* Not sure - TM03 ignores data/media errors while spacing */ + if (TM03_ERRS(tm)) { + TMREG(tm, RHR_ER1) |= TM_EOPI; /* What else to use? */ + TMREG(tm, RHR_STS) |= TM_SERR; + } +#endif + tm_ssta(tm); /* Update all status */ + tm_attn(tm); /* Done, signal attention */ + break; + + /* The remaining commands are all I/O xfer commands */ + + case TM_WRT: /* Write Forward */ + /* Possibilities: + ** (1) BAFC != # bytes from data channel sent to slave + ** (2) # bytes sent != # bytes slave actually wrote + ** (1) should cause a channel short-count error. + ** (2) is problematical. Channel data was already snarfed, so + ** causing a channel long-count error might confuse system; + ** no TM03 error seems quite right. + ** I'll make this give an OPI (operation incomplete) error. + */ + TMREG(tm, RHR_BAFC) = (TMREG(tm, RHR_BAFC) + tm->tm_bchs) & MASK16; + if (TMREG(tm, RHR_BAFC)) { + /* Frame count didn't match ctlr data count, so complain */ + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm_cmddon: WRT!=0: %#o]\r\n", + TMREG(tm, RHR_BAFC)); + (*tm->tm_dv.dv_ioend)(&tm->tm_dv, 1); /* Say stuff left */ + } else { + (*tm->tm_dv.dv_ioend)(&tm->tm_dv, 0); /* Say all's well */ + TMREG(tm, RHR_OFTC) &= ~TM_TFCS; /* Clear FCS bit */ + } + + /* Check against # frames actually written */ + if (tm->tm_bchs != TM03_FRMS(tm)) { + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm_cmddon: WRTbf: %#lo != %#lo]\r\n", + (long)tm->tm_bchs, (long)TM03_FRMS(tm)); + + /* Ugh, set BAFC to -<# frames unwritten> */ + TMREG(tm, RHR_BAFC) = (TM03_FRMS(tm) - tm->tm_bchs) & MASK16; + TMREG(tm, RHR_OFTC) |= TM_TFCS; /* Restore FCS bit */ + TMREG(tm, RHR_ER1) |= TM_EOPI; + TMREG(tm, RHR_STS) |= TM_SERR; + } + + /* Check and set general status. + ** No ATTN is signaled for I/O unless some error happened. + */ + tm_ssta(tm); + if (TMREG(tm, RHR_STS) & TM_SERR) /* If any errors, */ + tm_attn(tm); /* signal ATTN */ + break; + + case TM_WCF: /* Write Check Forward (same as Read Forward) */ + case TM_RDF: /* Read Forward */ + /* Find # frames read if any, then do channel xfer */ + tm_flsbuf(tm, 0); + break; + + case TM_WCR: /* Write Check Reverse (same as Read Reverse) */ + case TM_RDR: /* Read Data Reverse (not used by ITS) */ + tm_flsbuf(tm, 1); /* Read in reverse direction */ + break; + + default: + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[TM03 unknown scmd %#o]\r\n", cmd); + /* Let unknown commands clear GO bit, to avoid wedging */ + break; + } + + TMREG(tm, RHR_CSR) &= ~TM_1GO; /* Turn off GO bit in CSR */ +} + + +static void +tm_nxfn(register struct tmdev *tm) /* Non-Executable Function */ + +{ + TMREG(tm, RHR_ER1) |= TM_ENEF; /* Non Executable function */ + TMREG(tm, RHR_STS) |= TM_SERR; /* Error summary */ + tm_attn(tm); /* Send attention interrupt */ +} + +/* Send special attention interrupt +** Aside from errors it appears that this may be done whenever a spacing +** operation (as opposed to I/O) finishes. +*/ +static void +tm_attn(register struct tmdev *tm) +{ + TMREG(tm, RHR_STS) |= TM_SATA; + TMREG(tm, RHR_ATTN) |= tm->tm_bit; /* For our drive # */ + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm_attn: ON]"); + (*tm->tm_dv.dv_attn)(&(tm->tm_dv), 1); /* Assert ATTN */ +} + +/* Send slave status change and trigger interrupt */ +static void +tm_ssint(register struct tmdev *tm) +{ + TMREG(tm, RHR_STS) |= TM_SSSC; /* Slave status change */ + tm_attn(tm); +} + +/* ATTN Checks? + According to TM03 doc (p.4-44), ATTN is asserted under the +following conditions: + +1. At completion of an erase, space, or write-TM operation. +2. Upon initiation of a rewind command. +3. Upon loading a 1 into GO bit of CSR while an error condition exists. +4. Upon termination of an operation during which an error occurred or SSC + was asserted. +5. Upon termination of any operation during which END PT was asserted. + (Not clear if this includes BOT in reverse direction). + +*/ + +/* Read/Write data into memory using: +** Currently selected slave (cs2, tc) +** Frame count (fc) +** (20) Controller count & phys addr +** +** (11) Start addr in memory (ba) Byte address (4 bytes per PDP10 wd) +** (11) UBA mapping +** +** NEW REGIME: +** Asynch I/O operation requires a certain amount of trickery. +** For WRITE operations, the entire record is first acquired via the +** controller and read into a byte array before being sent to the slave. +** If doing asynch, control returns at this point. When the slave completes +** the record write, the event handler cleans up. +** +** For READ operations, the controller is first initialized (as a check for +** errors in setup), and the entire record is then read in from the slave +** (if doing asynch, control returns during this). When the slave completes +** the record read, the event handler carries out the controller I/O, possibly +** doing so in reverse order. +*/ +/* ITS notes: +** ITS never writes (and cannot read) records of more than +** 1024. words. It can however select 32-bit (industry-compatible) format +** using high 4 8-bit bytes instead of 36-bit core-dump format, which +** uses 5 frames per word. +** ITS programs (ie DUMP) generally don't care about record boundaries. +** They only note tape-marks (file EOFs). +*/ + +static int +tm_io(register struct tmdev *tm, + int dirf) /* +1 = Read Fwd, 0 = Write Fwd, -1 = Read Reverse */ +{ + int blkcnt; + int wrtf = (dirf == 0); /* TRUE if writing */ + int revf = (dirf < 0); /* TRUE if read reverse */ + +#if !KLH10_DEV_DPTM03 + register struct vmtape *t = &tm->tm_vmt; +#endif + + /* Start the tape going! */ + TMREG(tm, RHR_STS) &= ~(TM_SBOT|TM_SEOT|TM_STM); /* No BOT, EOT, EOF */ + + /* Now see if controller can set up transfer OK. + ** Note that in real life, I/O might already be initiated to device + ** before any controller problems are discovered! + */ + + /* Find # records controller wants us to xfer. Better be 1 for tape! */ + blkcnt = (*tm->tm_dv.dv_iobeg)(&tm->tm_dv, wrtf); + if (blkcnt != 1) { /* If screwed up somehow, */ + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm_io: bad rec cnt (%o)]\r\n", blkcnt); + if (blkcnt == 0) { + (*tm->tm_dv.dv_ioend)(&tm->tm_dv, 0); + return 0; + } + } + + /* Verify that data xfer has acceptable format */ + switch (tm->tm_sfmt) { + case TM_FCD: + case TM_FIC: + break; + default: /* Fail with a TM_EFMT error */ + TMREG(tm, RHR_ER1) |= TM_EFMT; + TMREG(tm, RHR_STS) |= TM_SERR; + tm_attn(tm); + (*tm->tm_dv.dv_drerr)(&tm->tm_dv); /* Stop channel xfer */ + return 0; + } + + if (wrtf) { + /* Writing record (forward only) */ + if (!tm_filbuf(tm)) /* Fill up record buffer */ + return 0; /* Some error, return */ +#if KLH10_DEV_DPTM03 + tm_dpcmd(tm, DPTM_WRT, tm->tm_bchs); /* Tell slave to write! */ +#else + vmt_rput(t, tm->tm_buff, tm->tm_bchs); +#endif + + } else { + /* Reading record (forward or reverse) */ +#if KLH10_DEV_DPTM03 + tm_dpcmd(tm, + (revf ? DPTM_RDR : DPTM_RDF), /* Tell slave to read! */ + DPTM_MAXRECSIZ); +# if 0 /* Synch hack! */ + dp_xswait(&(tm->tm_dp.dp_adr->dpc_todp)); +# endif +# else + if (revf) { + /* Simulate read-reverse by spacing backward one record, + reading it in, then backing up again. + But no need to read in again if BOT or EOF. + */ + if ((vmt_rspace(t, 1, 1)) + && (vmt_framecnt(t) == 1) + && !vmt_isateof(t) + && !vmt_isatbot(t) + && !vmt_errors(t)) { + long savcnt; + + vmt_rget(t, tm->tm_buff, DVTM_MAXRECSIZ); + savcnt = vmt_framecnt(t); /* Remember frames read */ + (void) vmt_rspace(t, 1, 1); /* Back up, clobbers fc */ + vmt_framecnt(t) = savcnt; /* Ugly hack to restore fc */ + } + } else { + vmt_rget(t, tm->tm_buff, DVTM_MAXRECSIZ); + } +#endif /* !KLH10_DEV_DPTM03 */ + + } + return 1; /* Proceed asynchronously */ +} + + +static int +tm_filbuf(register struct tmdev *tm) +{ + register int wc; + register unsigned char *buff = tm->tm_buff; + register unsigned char * (*fmtfunct)(unsigned char *, vmptr_t, int); + register long bwcnt, fcnt, totw; + vmptr_t vp; + + /* Find format conversion routine to use. + ** Note two things: (1) caller has already checked for validity, so + ** paranoia check defaults to Core-Dump rather than barfing. + ** (2) This calling style will not suffice for high-density format, which + ** if implemented will need to know whether it's at start or middle of + ** a double-word and thus must maintain external state (eg via a 4th arg) + */ + switch (tm->tm_sfmt) { + default: + case TM_FCD: fmtfunct = wdstofcd; break; + case TM_FIC: fmtfunct = wdstofic; break; + } + + /* Find total # words want to xfer. What we have is just a frame count, + ** so the # of words depends on how data is being + ** formatted (ie how many tape frames per word). + ** Note that the frame cnt is negative; a zero value is interpreted as + ** the maximum count, so there will always be at least one word to xfer. + */ + fcnt = -(TMREG(tm, RHR_BAFC) | ~(long)MASK16); /* Find # frames */ + bwcnt = fcnt / tm->tm_sfpw; /* Find # whole words */ + if (fcnt % tm->tm_sfpw) { /* See if partial word */ + ++bwcnt; /* Ugh, bump up */ + fprintf(DVDBF(tm), + "[tm_filbuf: Frames not mod-word (%ld.)]\r\n", (long)fcnt); + } + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm_filbuf: Write %ld frames]", fcnt); + + + /* Get 10's data channel info - buffer pointer and count */ + wc = (*tm->tm_dv.dv_iobuf)(&tm->tm_dv, 0, &vp); + + /* WC has # words to xfer on first pass (may be 0 if initial setup + ** failed). VP will always be set cuz writing ("channel skip" uses a + ** pattern of words, hence VP is never null). + */ + totw = bwcnt; /* Remember original count */ + while (wc && bwcnt) { + + if (wc > bwcnt) /* Apply frame counter limit */ + wc = bwcnt; + + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm_filbuf: %d %#lo]\r\n", + wc, (long)(vp - vm_physmap(0))); + + buff = (*fmtfunct)(buff, vp, wc); /* Convert words to bytes */ + + if (DVDEBUG(tm) & DVDBF_DATSHO) + tm_showbuf(tm, buff - (wc * tm->tm_sfpw), vp, wc, 0); + + bwcnt -= wc; + + /* Update controller/datachannel's notion of transfer, and set up + ** for next pass. Loop test will fail if WC set 0. + */ + wc = (*tm->tm_dv.dv_iobuf)(&tm->tm_dv, wc, &vp); + } + tm->tm_bchs = (totw - bwcnt) * tm->tm_sfpw; + + /* Xfer done; tm_bchs has total # of frames xferred and available. + ** If this is more than fcnt, trim it down; support odd-size writes. + */ + if (tm->tm_bchs > fcnt) + tm->tm_bchs = fcnt; + + /* But if this is LESS than fcnt, there's a channel problem - ran out of + ** data from channel too early. + ** It's unclear how to handle this at the TM03 end. + ** For the RH20: + ** when phys I/O has completed, can tell wordcount was short by + ** noticing that updating BAFC with tm_bchs doesn't make it zero. + ** Then need to invoke dv_ioend with a blockcount arg of 1 to indicate + ** there was still some stuff left the device wanted to do. + */ + return 1; +} + +/* TM_FLSBUF - Flush record buffer by copying it into 10's memory +*/ +static int +tm_flsbuf(register struct tmdev *tm, int revf) +{ + register int wc; + register long bwcnt, fcnt; + register unsigned char *buff = tm->tm_buff; + vmptr_t vp; + + /* Find frame count of record just gobbled, then derive total # words + ** to xfer, if any. This # depends on how data is being + ** formatted (ie how many tape frames per word). + ** Note number may be 0 if a tapemark, BOT/EOT, or error was seen. + */ + tm->tm_bchs = TM03_FRMS(tm); /* Get # frames in record */ + fcnt = tm->tm_bchs; /* Find # frames in record */ + bwcnt = fcnt / tm->tm_sfpw; /* Find # whole words */ + if (fcnt % tm->tm_sfpw) { /* See if partial word */ + ++bwcnt; /* Ugh, bump up */ + memset(buff+fcnt, 0, 4); /* Ensure leftover bytes clear */ + /* This assumes we're reading forward, but is harmless if it turns + ** out we're reading reverse, and should rarely happen anyway. + ** (See the fcdtowds() routine for more comments on this padding) + */ + } + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm_flsbuf: Read %ld frames]", fcnt); + + /* Report # frames in record, regardless of whether controller + ** accepts the data. (What else to do?) + */ + TMREG(tm, RHR_BAFC) = fcnt & MASK16; /* Report # frames read */ + + /* Set up 10's buffer pointer and count */ + wc = (*tm->tm_dv.dv_iobuf)(&tm->tm_dv, 0, &vp); + + /* WC has # words to xfer on first pass (may be 0 if initial setup + ** failed, or negative if channel is doing a reverse transfer.) + ** If VP is set, then it points to phys mem to xfer to/from. + */ + + if (!revf && wc > 0) { + register void (*fmtfunct)(vmptr_t, unsigned char *, int); + + /* Normal case, reading forward */ + + /* Find format conversion routine to use. + ** Note two things: (1) caller has already checked for validity, so + ** paranoia check defaults to Core-Dump rather than barfing. + ** (2) This calling style will not suffice for high-density format, + ** which if implemented will need to know whether it's at start or + ** middle of a double-word and thus must maintain external state + ** (eg via a 4th arg) + */ + switch (tm->tm_sfmt) { + default: + case TM_FCD: fmtfunct = fcdtowds; break; + case TM_FIC: fmtfunct = fictowds; break; + } + + for (; wc && bwcnt; ) { + + if (wc > bwcnt) /* Apply frame counter limit */ + wc = bwcnt; + + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm_flsbuf: %d %#lo]\r\n", + wc, (vp ? (long)(vp - vm_physmap(0)) : 0L)); + + if (vp) { /* VP may be NULL if skipping */ + (*fmtfunct)(vp, buff, wc); + if (DVDEBUG(tm) & DVDBF_DATSHO) + tm_showbuf(tm, buff, vp, wc, 0); + } + buff += tm->tm_sfpw * wc; + bwcnt -= wc; + + /* Update controller/datachannel's notion of transfer, and set up + ** for next pass. Loop test will fail if WC set 0. + */ + wc = (*tm->tm_dv.dv_iobuf)(&tm->tm_dv, wc, &vp); + } + + } else if (wc) { + register void (*revfmtfunct)(vmptr_t, unsigned char *, int, int); + + /* Ugh, doing some flavor of reverse read or reverse transfer. + ** Don't try to be super efficient here, do a little more testing + ** within the loops. + */ + switch (tm->tm_sfmt) { + default: + case TM_FCD: revfmtfunct = revfcdtowds; break; + case TM_FIC: revfmtfunct = revfictowds; break; + } + if (wc < 0) /* Invert count if reverse xfer */ + bwcnt = -bwcnt; + if (revf) /* Start at end of buffer if read-reverse */ + buff += fcnt; + for (; wc && bwcnt; ) { + if ((wc > 0) ? (wc > bwcnt) /* Apply frame counter limit */ + : (wc < bwcnt)) { + wc = bwcnt; + } + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm_flsbuf: %d %#lo]\r\n", + wc, (vp ? (long)(vp - vm_physmap(0)) : 0L)); + + if (vp) { /* VP may be NULL if skipping */ + (*revfmtfunct)(vp, buff, wc, revf); + if (DVDEBUG(tm) & DVDBF_DATSHO) + tm_showbuf(tm, buff, vp, wc, revf); + } + buff += (revf ? -tm->tm_sfpw : tm->tm_sfpw) * abs(wc); + bwcnt -= wc; + wc = (*tm->tm_dv.dv_iobuf)(&tm->tm_dv, wc, &vp); + } + } + + tm_ssta(tm); /* Set slave status bits (TM, BOT, etc) */ + if (TM03_ERRS(tm)) { + /* Set some kind of error bit here? */ + TMREG(tm, RHR_ER1) |= TM_EOPI; /* What else to use? */ + TMREG(tm, RHR_STS) |= TM_SERR; + } + + /* Now wrap up channel xfer, tell controller we're done */ + if (TMREG(tm, RHR_STS) & TM_SERR) { + (*tm->tm_dv.dv_drerr)(&tm->tm_dv); + tm_attn(tm); + + } else if (bwcnt) { + /* Channel problem, ran out of space from channel too early. */ + (*tm->tm_dv.dv_ioend)(&tm->tm_dv, 1); /* Say device wanted more */ + + } else { + /* Normal termination */ + (*tm->tm_dv.dv_ioend)(&tm->tm_dv, 0); /* Win, all blks done */ + } + + return 1; +} + +static void +tm_showbuf(register struct tmdev *tm, + register unsigned char *ucp, + register vmptr_t vp, + register int wc, + int revf) +{ + register w10_t w; + register int fpw = tm->tm_sfpw; + + + + /* Back up appropriately if Read Reverse or reverse chan xfer */ + if (wc < 0) { + wc = -wc; + vp -= wc; + } + if (revf) + ucp -= fpw * wc; + + for (; --wc >= 0; ++vp, ucp += fpw) { + w = vm_pget(vp); + fprintf(DVDBF(tm), "[TM03: %#lo/ %6lo,,%6lo %3o %3o %3o %3o", + (long)(vp - vm_physmap(0)), (long)LHGET(w), (long)RHGET(w), + ucp[0], ucp[1], ucp[2], ucp[3]); + if (fpw > 4) + fprintf(DVDBF(tm), " %3o", ucp[4]); + fprintf(DVDBF(tm), "]\r\n"); + } +} + +/* Copy words to Core-Dump record format +*/ +static unsigned char * +wdstofcd(register unsigned char *ucp, + register vmptr_t vp, + register int wc) +{ + register w10_t w; + + for (; --wc >= 0; ++vp) { + w = vm_pget(vp); + *ucp++ = (LHGET(w)>>10) & 0377; + *ucp++ = (LHGET(w)>> 2) & 0377; + *ucp++ = ((LHGET(w)&03)<<6) | ((RHGET(w)>>12)&077); + *ucp++ = (RHGET(w)>>4) & 0377; + *ucp++ = RHGET(w) & 017; + } + return ucp; +} + +/* Copy words to Industry-Compatible record format +*/ +static unsigned char * +wdstofic(register unsigned char *ucp, + register vmptr_t vp, + register int wc) +{ + register w10_t w; + + for (; --wc >= 0; ++vp) { + w = vm_pget(vp); + *ucp++ = (LHGET(w)>>10) & 0377; + *ucp++ = (LHGET(w)>> 2) & 0377; + *ucp++ = ((LHGET(w)&03)<<6) | ((RHGET(w)>>12)&077); + *ucp++ = (RHGET(w)>>4) & 0377; + /* Ignore bottom 4 bits */ + } + return ucp; +} + +/* Copy Core-Dump record format to words +** Note that there is no length check for the bytes; it is assumed +** that there are always enough valid bytes to build an integral number +** of words. +** Since this is not always true for tape record lengths, there is code +** in tm_flsbuf() that checks for odd lengths and ensures that there +** are enough extra zero bytes to provide for a nice clean full word +** of data at the end of a transfer. 4 extra bytes suffice because +** currently 5-bytes-per-word is the largest valid format. +*/ +static void +fcdtowds(register vmptr_t vp, + register unsigned char *ucp, + register int wc) +{ + register w10_t w; + + for (; --wc >= 0; ++vp, ucp += 5) { + LRHSET(w, + (((uint18)(ucp[0] & 0377) << 10) + | ((ucp[1] & 0377) << 2) + | ((ucp[2] >> 6) & 03)), + (((uint18)(ucp[2] & 077) << 12) + | ((ucp[3] & 0377) << 4) + | (ucp[4] & 017)) + ); + vm_pset(vp, w); + } +} + +/* Copy Industry-Compatible record format to words +** Same comments as for fcdtowds() above. +*/ +static void +fictowds(register vmptr_t vp, + register unsigned char *ucp, + register int wc) +{ + register w10_t w; + + for (; --wc >= 0; ++vp, ucp += 4) { + LRHSET(w, + (((uint18)(ucp[0] & 0377) << 10) + | ((ucp[1] & 0377) << 2) + | ((ucp[2] >> 6) & 03)), + (((uint18)(ucp[2] & 077) << 12) + | ((ucp[3] & 0377) << 4)) + /* No byte for bottom 4 bits */ + ); + vm_pset(vp, w); + } +} + +/* REVERSE Copy Core-Dump record format to words +** The same issues regarding odd record lengths apply here as for +** fcdtowds(). However, for the read-reverse case, bytes are read out in +** REVERSE order. To provide for the extra zero padding this requires +** at the start of the record, the dptm_revpad[] array exists in the DPTM +** structure and uses the assumption that the record buffer immediately +** follows this structure, and the bytes are cleared just after the call +** to dp_init in tm03_init. +*/ +static void +revfcdtowds(register vmptr_t vp, + register unsigned char *ucp, + register int wc, /* Negative if reverse chan xfer */ + register int revf) /* TRUE if Read-Reverse */ +{ + register w10_t w; + + while (wc) { + if (revf) + ucp -= 5; + LRHSET(w, + (((uint18)(ucp[0] & 0377) << 10) + | ((ucp[1] & 0377) << 2) + | ((ucp[2] >> 6) & 03)), + (((uint18)(ucp[2] & 077) << 12) + | ((ucp[3] & 0377) << 4) + | (ucp[4] & 017)) + ); + if (!revf) + ucp += 5; + vm_pset(vp, w); + if (wc < 0) + --vp, ++wc; + else + ++vp, --wc; + } +} + +/* REVERSE Copy Industry-Compatible record format to words +** Same comments as for revfcdtowds() above. +*/ +static void +revfictowds(register vmptr_t vp, + register unsigned char *ucp, + register int wc, /* Negative if reverse chan xfer */ + register int revf) /* TRUE if Read-Reverse */ +{ + register w10_t w; + + while (wc) { + if (revf) + ucp -= 4; + LRHSET(w, + (((uint18)(ucp[0] & 0377) << 10) + | ((ucp[1] & 0377) << 2) + | ((ucp[2] >> 6) & 03)), + (((uint18)(ucp[2] & 077) << 12) + | ((ucp[3] & 0377) << 4)) + /* No byte for bottom 4 bits */ + ); + if (!revf) + ucp += 4; + vm_pset(vp, w); + if (wc < 0) + --vp, ++wc; + else + ++vp, --wc; + } +} + + +/* Space forward or reverse by the # of records/tapemarks specified +** by the negative count in UB_TMFC (not WC!) +** Apparently, what stops the spacing is a transition to 0, not a transition +** to a positive state; at least one record/tapemark is always spaced. +** The TM03 will abort if it encounters a tapemark; the read +** position will be past the tapemark, but the frame count will NOT +** include the tapemark; it only counts valid records. +** Also, media errors are ignored while spacing; they do not cause +** an error. +*/ +static void +tm_space(register struct tmdev *tm, int revf) +{ + register uint18 cnt; + + /* Must ensure that frame count reg was set; FCS bit tells + ** us whether a valid count exists. + */ + if (!(TMREG(tm, RHR_OFTC)&TM_TFCS)) { + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[tm_space: NEF - FCS=0]\r\n"); + tm_nxfn(tm); /* Give error "Non-Executable Function" */ + return; + } + + cnt = -(TMREG(tm, RHR_BAFC) | ~MASK16); + if (!cnt) cnt = MASK16+1; /* Ugh, max count */ + + if (DVDEBUG(tm)) + fprintf(DVDBF(tm), "[TM03 Space %s: %ld]\r\n", + (revf ? "Rev" : "Fwd"), (long)cnt); + + /* Tape motion initiated, change status bits */ + TMREG(tm, RHR_STS) &= ~(TM_SBOT|TM_STM|TM_SEOT); +#if KLH10_DEV_DPTM03 + TMREG(tm, RHR_STS) &= ~TM_SDRY; /* Drive busy, note GO still on! */ + TMREG(tm, RHR_STS) |= TM_SPIP; /* Positioning in progress */ + tm_dpcmd(tm, /* Send spacing command to DP */ + (revf ? DPTM_SPR : DPTM_SPF), + (size_t)cnt); +#else + + if (!vmt_rspace(&tm->tm_vmt, revf, (unsigned long)cnt)) { /* Do it */ + /* Internal error */ + tm_nxfn(tm); + return; + } +#endif /* !KLH10_DEV_DPTM03 */ +} + +#endif /* KLH10_DEV_TM03 */ diff --git a/src/dvtm03.h b/src/dvtm03.h new file mode 100644 index 0000000..7c607f9 --- /dev/null +++ b/src/dvtm03.h @@ -0,0 +1,248 @@ +/* DVTM03.H - RH11/TM03 Controller definitions +*/ +/* $Id: dvtm03.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dvtm03.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ +/* +** Portions of this file were derived from AI:SYSTEM;TM03S DEFS4 +*/ + +#ifndef DVTM03_INCLUDED +#define DVTM03_INCLUDED 1 + +#ifdef RCSID + RCSID(dvtm03_h,"$Id: dvtm03.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +#include "dvrh20.h" /* Temp: get RHR ext reg defs */ + +/* Initialization & sole entry point for TM02/3 driver +*/ +#include "kn10dev.h" +extern struct device *dvtm03_create(FILE *f, char *s); + + +/* NOTE: + Of the RH20 external registers, the following are the ones +recognized (or not) by the TM02/TM03: +*/ +#if 0 + RHR_CSR 0 /* R/W CS1 Control/command */ + RHR_STS 01 /* RO [I2] STS Status */ + RHR_ER1 02 /* R/W ER1 Error 1 */ + RHR_MNT 03 /* R/W [-2] MNT Maintenance */ + RHR_ATTN 04 /* R/W [I2] ATN? Attention Summary */ + RHR_BAFC 05 /* R/W [I2] ADR Block Address or Frame Count */ + RHR_DT 06 /* RO [I2] TYP Drive Type */ + RHR_LAH 07 /* RO LAH Current BlkAdr or R/W ChkChr */ + --*-- RHR_ER2 010 /* R/W ER2 Error 2 */ + RHR_OFTC 011 /* R/W OFS Offset or TapeControl */ + --NO-- RHR_DCY 012 /* R/W CYL Desired Cylinder Addr */ + --NO-- RHR_CCY 013 /* RO [-2] CCY Current Cylinder Addr */ + --NO-- RHR_SN 014 /* RO [-2] SER Serial Number */ + --NO-- RHR_ER3 015 /* R/W ER3 Error 3 */ + --NO-- RHR_EPOS 016 /* RO POS ECC Position */ + --NO-- RHR_EPAT 017 /* RO PAT ECC Pattern */ +#endif /* 0 */ +/* + +--*-- = The RH20 doc (p.2-20) says the TM02 uses reg 014 for SN, and 010 is + unused. + HOWEVER, the TM03 doc says reg 010 is SN, and 014 is unused; this also +agrees with the TOPS-20 monitor code. So RHR_ER2 is used in the TM02 +emulation to refer to the transport serial number, rather than RHR_SN. + + (The doc agrees on all other regs.) +*/ + +#if 0 /* ITS AI RH11/TM03 defs */ + +/* RH11/TM03 Interrupt vector: */ +#define UB_TM03_BR 6 +#define UB_TM03_VEC 0224 /*(224/4 = 45) Interrupts occur on level 6 */ + /* (high priority) on UBA #1. */ +#ifndef UB_TM03 +# define UB_TM03 0772440 /* Unibus address of first register */ +#endif +#endif /* 0 */ + +/* KLH: There is a source/binary inconsistency here. +** TM03S.DEFS3 defines the base address as 772440 and that is what +** the last AI ITS (1643) uses. +** This is also what the DEC T20 code expects. +** So that's what I'm using. +** but TM03S.DEFS4 defines it as 772400 for some reason??? +** +** Also, all of those files are wrong about claiming it is on UBA #1; +** as far as I can tell it is always on UBA #3 (DEC T20 confirms this). +*/ + +/* RH11/TM03 Unibus register addresses: */ +/* Note: CS1 bits are shared by controller (RH) & drive (DR) */ + + +/* CS1: (RH/DR R/W) CTRL AND STATUS 1. (RH11:0, RH20:0) */ + +# define TM_1SC (1<<15) /* RH: Special Condition */ +# define TM_1TE (1<<14) /* RH: Transfer Error */ +# define TM_1MP (1<<13) /* RH: Massbus Control Bus Parity Error */ +# define TM_1DA (1<<11) /* DR: Drive Available */ +# define TM_1A7 (1<<9) /* RH: UB Address Extension Bit 17 */ +# define TM_1A6 (1<<8) /* RH: UB Address Extension Bit 16 */ +# define TM_1RY (1<<7) /* RH: Ready */ +# define TM_1IE (1<<6) /* RH: Interrupt Enable */ +# define TM_1CM 077 /* DR: Bits 0-5 specify commands. */ +# define TM_1GO 01 /* DR: GO bit */ + +/* Commands with bit 0 (GO) included: */ + +# define TM_NOP 01 /* No Operation */ +# define TM_UNL 03 /* Unload */ +# define TM_REW 07 /* Rewind */ +# define TM_CLR 011 /* Formatter clear (reset errs etc) */ +# define TM_RIP 021 /* Read-In Preset (not used by T20) */ +# define TM_ER3 025 /* Erase three inch gap */ +# define TM_WTM 027 /* Write Tape Mark */ +# define TM_SPF 031 /* Space Forward */ +# define TM_SPR 033 /* Space Reverse */ +# define TM_WCF 051 /* Write Check FOrward */ +# define TM_WCR 057 /* Write Check Reverse */ +# define TM_WRT 061 /* Write Forward */ +# define TM_RDF 071 /* Read Forward */ +# define TM_RDR 077 /* Read Data Reverse */ + +/* WC: (RH R/W) WORD COUNT. (RH11:1, RH20:-) */ +/* BA: (RH R/W) UNIBUS ADDRESS. (RH11:2, RH20:-) */ +/* FC: (DR R/W) TAPE FRAME COUNT (RH11:3, RH20:5) */ + +/* CS2: (RH R/W) CTRL AND STATUS 2. (RH11:4, RH20:-) */ + +# define TM_2DL (1<<15) /* Data Late */ +# define TM_2UP (1<<13) /* Unibus Parity Error */ +# define TM_2NF (1<<12) /* Non-existant Formatter */ +# define TM_2NM (1<<11) /* %TMBA is NXM during DMA */ +# define TM_2PE (1<<10) /* Program Error */ +# define TM_2MT (1<<9) /* Missed Transfer */ +# define TM_2MP (1<<8) /* Massbus Data Bus Parity Error */ +# define TM_2OR (1<<7) /* Output Ready (for Silo buffer diag.) */ +# define TM_2IR (1<<6) /* Input Ready (for Silo buffer diag.) */ +# define TM_2CC (1<<5) /* Controller Clear */ +# define TM_2PT (1<<4) /* Parity Test */ +# define TM_2AI (1<<3) /* Unibus Address Increment Inhibit */ +# define TM_2FS 07 /* Formatter select */ + +/* FS: (DR RO) FORMATTER STATUS. (RH11:5, RH20:1) "STS" for RP */ + /* SS=SelectedSlave, S=AnySlave, M=TM03 fmtr */ +# define TM_SATA (1<<15) /* 100000 (M) Attention Active */ +# define TM_SERR (1<<14) /* 040000 (M) Error Summary */ +# define TM_SPIP (1<<13) /* 020000 (M/SS) Positioning in Progress */ +# define TM_SMOL (1<<12) /* 010000 (SS) Medium On-Line */ +# define TM_SWRL (1<<11) /* 04000 (SS) Write Locked */ +# define TM_SEOT (1<<10) /* 02000 (SS) End of Tape */ + /* (1<<9)*//* unused */ +# define TM_SDPR (1<<8) /* 0400 (M) Formatter Present (hardwired) */ +# define TM_SDRY (1<<7) /* 0200 (M) Drive/Formatter Ready */ +# define TM_SSSC (1<<6) /* 0100 (S) Any-Slave Status Change */ +# define TM_SPES (1<<5) /* 040 (SS) Phase Encoded (1600BPI) Mode */ +# define TM_SDWN (1<<4) /* 020 (SS) Settling Down */ +# define TM_SIDB (1<<3) /* 010 (M) PE Ident Burst Detected */ +# define TM_STM (1<<2) /* 04 (M) Tape Mark detected */ +# define TM_SBOT (1<<1) /* 02 (SS) Beginning of Tape */ +# define TM_SSLA (1<<0) /* 01 (SS) Slave Attention (came on-line + ** manually while selected; + ** cleared by init & drive clr) + */ + +/* ERR: (DR RO) ERROR 1. (RH11:6, RH20:2) (ER1) */ + /* B=terminates in-progress data xfer, A=no */ +# define TM_ECDE (1<<15) /* (A) Correctable Data/CRC Error */ +# define TM_EUNS (1<<14) /* (B) Unsafe */ +# define TM_EOPI (1<<13) /* (B) Operation Incomplete */ +# define TM_EDTE (1<<12) /* (B) Controller Timing Error */ +# define TM_ENEF (1<<11) /* (B) Non Executable Function */ +# define TM_ECS (1<<10) /* (A) Correctable Skew/Illegal Tape Mark Error */ +# define TM_EFCE (1<<9) /* (A) Frame Count Error */ +# define TM_ENSG (1<<8) /* (A) Non-standard Gap */ +# define TM_EPEF (1<<7) /* (A) PE Format/LRC Error */ +# define TM_EINC (1<<6) /* (A) Incorrectable Data/Hard Error */ +# define TM_EMDP (1<<5) /* (A) Massbus Data Parity Error */ +# define TM_EFMT (1<<4) /* (B) Format Select Error */ +# define TM_EMCP (1<<3) /* (A) Massbus Control Parity Error */ +# define TM_ERMR (1<<2) /* (A) Register Modification Refused */ +# define TM_EILR (1<<1) /* (A) Illegal (non-ex) Register */ +# define TM_EILF (1<<0) /* (B) Illegal Function code */ +# define TM_EHD 044077 /* Hard errors - US,NEF,MD,FS,MC,RMR,ILR,ILF */ + +/* ASN: (DR* R/W) ATTENTION SUMMARY. (RH11:7, RH20:4) (ATN) */ + /* Each bit 7-0 corresponds to a formatter asserting ATA. */ + /* Or is that "slave", because this is a drive register??? */ + /* Most likely formatter, akin to RP drives */ + +/* CCR: (DR RO) CHECK CHARACTER REGISTER (RO) (RH11:010, RH20:7) (LAH) */ +# define TM_CDP (1<<8) /* Dead Track Parity/CRC Parity */ +# define TM_CEI 0177 /* Error Information */ + +/* BUF: (DR R/W) DATA BUFFER. (RH11:011, RH20:-) */ +/* MNT: (DR R/W) MAINTENANCE. (RH11:012, RH20:3) */ + +/* TYP: (DR RO) DRIVE TYPE. (RH11:013, RH20:6) */ + +# define TM_DTNS (1<<15) /* 2.7 Not Sector addressed (always on) */ +# define TM_DTTA (1<<14) /* 2.6 Tape (always on) */ +# define TM_DTMH (1<<13) /* 2.5 Moving Head (always off) */ +# define TM_DT7C (1<<12) /* 2.4 7-track (always off) */ +# define TM_DTDC (1<<11) /* 2.3 Dual controller (always off) */ +# define TM_DTSS (1<<10) /* 2.2 Slave Selected (Slave Present) */ + /* (1<<9) */ /* 2.1 Unused */ +# define TM_DTDT 0777 /* 1.9 - 1.1 Selected Slave Type Number. */ +# define TM_DT00 010 /* 10= No slave */ +# define TM_DT16 011 /* 11= TE16 45 in/sec */ +# define TM_DT45 012 /* 12= TU45 75 in/sec */ +# define TM_DT77 014 /* 14= TU77 125 in/sec */ + +# define TM_DTTM03 050 /* Bit set if TM03, else TM02 */ +# define TM_DTTM02 010 /* lowest & highest TM03 types are 050-057 + ** " & " TM02 " " 010-017 + */ + +/* SER: (DR RO) SERIAL NUMBER. (RH11:014, RH20:010) */ + + +/* TC: (DR R/W) TAPE CONTROL REGISTER (RH11:015, RH20:011) (OFTC) */ + /* Selects & configures a slave transport */ +# define TM_TAC (1<<15) /* (RO) Acceleration (not up to speed) */ +# define TM_TFCS (1<<14) /* Frame Count Status */ +# define TM_TSA (1<<13) /* Slave Address (selected slave) Changed */ +# define TM_TEA (1<<12) /* Enable Abort on data transfer error */ + /* (ie ERR bits 15,7,6,5) */ +# define TM_TDS (07<<8) /* Density Select Field */ +# define TM_D02 00 /* 200 BPI (TM02 only) */ +# define TM_D05 01 /* 556 BPI (TM02 only) */ +# define TM_D08 02 /* 800 BPI NRZI (ITS thinks this is 3?) */ +# define TM_D16 04 /* 1600 BPI PE */ +# define TM_TFS (017<<4) /* Format Select */ +# define TM_FCD 00 /* PDP10 Core Dump */ +# define TM_FAA 02 /* ANSI-ASCII (TM02 only) */ +# define TM_FIC 03 /* Industry Compatible (32 bit mode) */ +# define TM_TEP (1<<3) /* Even Parity (for 800 NRZI only) */ +# define TM_TTS 07 /* Transport (Slave) Select */ + +#endif /* ifndef DVTM03_INCLUDED */ diff --git a/src/dvuba.c b/src/dvuba.c new file mode 100644 index 0000000..acd6c6f --- /dev/null +++ b/src/dvuba.c @@ -0,0 +1,481 @@ +/* DVUBA.C - KLH10 Emulation of KS10 Unibus Adapters +*/ +/* $Id: dvuba.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dvuba.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* +** Provides interface between I/O instructions and UNIBUS devices, +** including the unibus adapters as devices in their own right. +** +** Note that unibus addresses and register values are different in +** the UBA world than they are for "normal" unibus devices. +** In particular, for the UBA code: +** Addresses: 18 bits (RH of IO-address) (18 for real devs) +** Registers: 32 bits on read; 18 on write (16 for real devs) +*/ + +#include "klh10.h" + +#if !KLH10_CPU_KS && CENV_SYS_DECOSF + /* Stupid gubbish needed to prevent OSF/1 AXP compiler from + ** halting merely because compiled file is empty! + */ +static int decosfcclossage; +#endif + +#if KLH10_CPU_KS /* Moby conditional for entire file */ + +#include /* For stderr if buggy */ + +#include "kn10def.h" +#include "kn10ops.h" +#include "kn10dev.h" +#include "dvuba.h" + +#ifdef RCSID + RCSID(dvuba_c,"$Id: dvuba.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Imported functions */ +extern void pi_devupd(void); + +/* Pre-declarations */ +static void ubasta_write(struct ubctl *, h10_t); +static uint32 ubapag_read(struct ubctl *, dvuadr_t); + + +#define PILEV_UB1REQ cpu.pi.pilev_ub1req +#define PILEV_UB3REQ cpu.pi.pilev_ub3req + +/* Unibus Adapter data structures. +** Leave external so CPU can access the PI request status. +** Easier debugging, too. +*/ +struct ubctl dvub1, dvub3; + +static void ub_pifun(struct device *, int); + +static uint32 ub0_read(dvuadr_t); /* Special rtns for controller #0 */ +static void ub0_write(dvuadr_t, h10_t); + + +/* DVUB_INIT - Initialize unibus code & data & devices +*/ +void +dvub_init(void) +{ + memset((char *)&dvub1, 0, sizeof(dvub1)); + memset((char *)&dvub3, 0, sizeof(dvub3)); + + dvub1.ubn = 1; + dvub3.ubn = 3; + + /* Remember place to deposit our PI requests with CPU. This is + ** something of a hack, but will do for now. + */ + dvub1.ubpireqs = &cpu.pi.pilev_ub1req; + dvub3.ubpireqs = &cpu.pi.pilev_ub3req; +} + + +int +dvub_add(FILE *of, + register struct device *d, + int ubn) +{ + register struct ubctl *ub; + register int i; + + switch (ubn) { + case 1: ub = &dvub1; break; + case 3: ub = &dvub3; break; + default: + fprintf(of, "Cannot bind device to unibus #%d - only 1 and 3 allowed.\n", + ubn); + return FALSE; + } + + if (ub->ubndevs >= KLH10_DEVUBA_MAX) { + fprintf(of, "Too many unibus #%d devices (limit is %d)\n", + ubn, KLH10_DEVUBA_MAX); + return FALSE; + } + + /* Verify that device registers don't conflict with an existing device */ + for (i = 0; i < ub->ubndevs; ++i) { + register struct device *d2 = ub->ubdevs[i]; + + /* For each existing dev, see if 1st addr comes after new dev's last, + * or last addr comes before new dev's first. + */ + if ((d->dv_aend <= d2->dv_addr) /* Comes after? */ + || (d->dv_addr >= d2->dv_aend)) { /* Comes before? */ + continue; /* OK, keep going */ + } + + /* Overlap -- complain and fail */ + fprintf(of, "Cannot bind device - Unibus register overlap:\r\n\ + Device %-6s %06lo-%06lo\r\n\ + Device %-6s %06lo-%06lo\r\n", + d->dv_name, (long)d->dv_addr, (long)d->dv_aend, + d2->dv_name, (long)d2->dv_addr, (long)d2->dv_aend); + return FALSE; + } + + d->dv_uba = ub; /* Point to right unibus controller */ + d->dv_pifun = ub_pifun; /* Use this to handle PI reqs */ + + i = ub->ubndevs; + ub->ubdevs[i+1] = NULL; /* Set fence in array */ + ub->ubdevs[i] = d; + ub->ubdvadrs[i*2] = d->dv_addr; + ub->ubdvadrs[(i*2)+1] = d->dv_aend; + ub->ubndevs++; + return TRUE; +} + +/* UB_PIFUN - Called from devices with BR # (int level) to assert. +** If nonzero, maps BR level to PI level and triggers PI. +** If zero, turns off PI request. +** Sorta painful cuz must check all other devices to be sure +** it's OK to turn off the corresponding PI level request. +*/ + +static void +ub_pifun(register struct device *d, + int br) +{ + register struct ubctl *ub = d->dv_uba; /* Get right UBA for dev */ + register int lev; + register struct device **dp; + + switch (br) { + case 0: /* Turn off request for this device */ + d->dv_pireq = 0; /* Turn off for specific device */ + + /* Now propagate turnoff up the chain */ + + /* Find new outstanding level, if any. + ** This scan is a bit ugly. + */ + lev = 0; + for (dp = ub->ubdevs; *dp; ++dp) /* Check all known devs */ + lev |= (*dp)->dv_pireq; + + if (lev == *(ub->ubpireqs)) /* Compare new level with current */ + return; /* No change, do nothing */ + *(ub->ubpireqs) = lev; /* Changed! Set new */ + pi_devupd(); /* Update main PI state */ + return; + + case 4: + case 5: + lev = ub->ublolev; /* BRs 4 and 5 are mapped to "low" PI */ + break; + case 6: + case 7: + lev = ub->ubhilev; /* BRs 6 and 7 are mapped to "high" PI */ + break; + default: + panic("ub_pireq: unknown BR level: %d", br); + } + + /* If broke out of switch, triggering interrupt at level "lev" */ + if (!lev) /* UBA may have no interrupt mapping */ + return; + + d->dv_pireq = lev; /* Set PI request for specific device */ + if (*(ub->ubpireqs) & lev) /* This level PI already requested? */ + return; /* Yep, needn't yell at CPU again */ + + *(ub->ubpireqs) |= lev; /* Nope, tell CPU about new PI! */ + pi_devupd(); /* Update main PI state */ +} + + +/* UB_DEVFIND - Look up device given its controller and unibus address. +** This is a slow but sure crock for now; it can be done much +** more cleverly with a hash table or even a sorted binary-searched +** order at runtime when devices are configured. Later... +*/ +static struct device * +ub_devfind(register struct ubctl *ub, + register dvuadr_t addr) +{ + register int i = 0, n = ub->ubndevs; + register dvuadr_t *ua = ub->ubdvadrs; + + for (; i < n; ua += 2, ++i) { + if (ua[0] <= addr && addr < ua[1]) + return ub->ubdevs[i]; + } + return NULL; /* None found */ +} + +/* Auxiliaries */ + +#define ubasta_read(ub) ((uint32)((ub)->ubsta)) +#define ubapag_write(ub,addr,val) ((ub)->ubpmap[(addr) - UB_UBAPAG] = (val)) + +static void +ub_badctl(char *fn, w10_t ioaddr, int bflag) +{ + fprintf(stderr, "%s: Bad UB ctlr in IO addr: %lo,,%lo\r\n", + fn, (long)LHGET(ioaddr), (long)RHGET(ioaddr)); + pag_iofail(((paddr_t)LHGET(ioaddr))<<18 | RHGET(ioaddr), bflag); +} + +static void +ub_badaddr(char *fn, dvuadr_t uaddr, int bflag) +{ + fprintf(stderr, "%s: Bad unibus IO addr: %lo\r\n", fn, (long)uaddr); + pag_iofail((paddr_t)uaddr, bflag); +} + +/* UB_READ - Called by KS10 IO instructions to read the Unibus. +** UB_WRITE - Ditto to write +*/ +uint32 +ub_read(register w10_t ioaddr) +{ + switch (LHGET(ioaddr)) { /* Check controller # */ + case 0: return ub0_read((dvuadr_t)RHGET(ioaddr)); + case 1: return uba_read(&dvub1, (dvuadr_t)RHGET(ioaddr)); + case 3: return uba_read(&dvub3, (dvuadr_t)RHGET(ioaddr)); + } + ub_badctl("ub_read", ioaddr, FALSE); + return 0; /* Never returns */ +} + +uint32 +ub_bread(register w10_t ioaddr) +{ + register dvuadr_t uaddr; + register uint32 reg; + + uaddr = RHGET(ioaddr) & ~01; /* Get address w/o low bit */ + switch (LHGET(ioaddr)) { /* Check controller # */ + case 0: reg = ub0_read(uaddr); break; + case 1: reg = uba_read(&dvub1, uaddr); break; + case 3: reg = uba_read(&dvub3, uaddr); break; + default: + ub_badctl("ub_bread", ioaddr, TRUE); + /* Never returns */ + } + if (RHGET(ioaddr) & 01) + reg = reg >> 8; + return reg & 0377; +} + +void +ub_write(register w10_t ioaddr, + h10_t val) +{ + switch (LHGET(ioaddr)) { /* Check controller # */ + case 0: ub0_write((dvuadr_t)RHGET(ioaddr), val); break; + case 1: uba_write(&dvub1, (dvuadr_t)RHGET(ioaddr), val); break; + case 3: uba_write(&dvub3, (dvuadr_t)RHGET(ioaddr), val); break; + default: + ub_badctl("ub_write", ioaddr, FALSE); + /* Never returns */ + } +} + +void +ub_bwrite(register w10_t ioaddr, + register h10_t val) +{ + register dvuadr_t uaddr; + register uint32 mask; + + if ((uaddr = RHGET(ioaddr)) & 01) { + uaddr &= ~01; + mask = 0377 << 8; + val <<= 8; + } else { + mask = 0377; + } + val &= mask; + + switch (LHGET(ioaddr)) { /* Check controller # */ + case 0: ub0_write(uaddr, (ub0_read(uaddr) & ~mask) | val); break; + case 1: uba_write(&dvub1, uaddr, + (uba_read(&dvub1, uaddr) & ~mask) | val); break; + case 3: uba_write(&dvub3, uaddr, + (uba_read(&dvub3, uaddr) & ~mask) | val); break; + default: + ub_badctl("ub_bwrite", ioaddr, TRUE); + /* Never returns */ + } +} + +static uint32 +ub0_read(dvuadr_t addr) +{ + switch (addr) { + case UB_KSECCS: /* Memory Status Register */ + return 0; /* Ignore writes, return 0 on reads */ + } + ub_badaddr("ub0_read", addr, FALSE); + return 0; /* Never returns */ +} + +static void +ub0_write(dvuadr_t addr, + h10_t val) +{ + switch (addr) { + case UB_KSECCS: /* Memory Status Register */ + return; /* Ignore writes */ + } + ub_badaddr("ub0_write", addr, FALSE); + return; /* Never returns */ +} + +uint32 +uba_read(register struct ubctl *ub, + dvuadr_t addr) +{ + register struct device *d; + + if (d = ub_devfind(ub, addr)) + return (uint32) (*(d->dv_read))(d, addr); + + switch (addr) { + case UB_UBASTA: /* Unibus Status Register */ + return ubasta_read(ub); + + case UB_UBAMNT: /* Unibus Maintenance Register */ + return 0; /* Ignore writes, return 0 on read */ + } + + if (UB_UBAPAG <= addr && addr < UB_UBAEND) { + return ubapag_read(ub, addr); + } + ub_badaddr("uba_read", addr, FALSE); + return 0; /* Never returns */ +} + +void +uba_write(register struct ubctl *ub, + register dvuadr_t addr, + h10_t val) /* UBA itself can take 18 bits */ +{ + register struct device *d; + + if (d = ub_devfind(ub, addr)) { + (*(d->dv_write))(d, addr, (dvureg_t)val); + return; + } + + switch (addr) { + case UB_UBASTA: /* Unibus Status Register */ + ubasta_write(ub, val); + return; + + case UB_UBAMNT: /* Unibus Maintenance Register */ + return; /* Ignore writes, return 0 on read */ + } + + if (UB_UBAPAG <= addr && addr < UB_UBAEND) { + ubapag_write(ub, addr, val); + return; + } + ub_badaddr("uba_write", addr, FALSE); +} + +/* Write status reg. +** Note corresponding ubasta_read() is a macro. +*/ +static void +ubasta_write(register struct ubctl *ub, + register h10_t val) +{ + register h10_t clrbits, setbits; + + /* Find bits to clear */ + clrbits = (val & (UBA_BTIM|UBA_BBAD|UBA_BPAR|UBA_BNXD)) | UBA_BPWR + | (UBA_BDXF|UBA_BINI|UBA_BPIH|UBA_BPIL); + + /* Find bits to set */ + setbits = (val & (UBA_BDXF|UBA_BINI|UBA_BPIH|UBA_BPIL)); + ub->ubsta = (ub->ubsta & ~clrbits) | setbits; + if (ub->ubsta & UBA_BINI) { + /* Do something to initialize this unibus? + ** Note: ITS source says can't both init and set PI levels, + ** hence the clearing done here for sake of emulation. + */ + val = (ub->ubsta &= ~(UBA_BINI|UBA_BPIH|UBA_BPIL)); + } + ub->ubhilev = pilev_bits[(val & UBA_BPIH)>>3]; + ub->ublolev = pilev_bits[val & UBA_BPIL]; +} + +/* Read paging regs. +** Note corresponding ubapag_write() is a macro +*/ +static uint32 +ubapag_read(register struct ubctl *ub, + dvuadr_t addr) +{ + register uint32 wval; + register uint18 ret; + + /* Reading, must get bits in screwy format */ + ret = ub->ubpmap[addr - UB_UBAPAG]; + wval = ((ret & (UBA_QRPW|UBA_Q16B|UBA_QFST|UBA_QVAL)) >> 5) + | UBA_PPVL /* Parity always valid */ + | ((ret & UBA_QPAG) >> 9); + wval = (wval << 18) | (((ret & UBA_QPAG) << 9) & H10MASK); + return wval; +} + +/* UBn_PIVGET - Called by PI system when responding to a PI request. +** Given a PI level mask (1 bit set), +** returns vector address for the "closest" device currently requesting +** an interrupt on that level. If none, returns 0. +*/ +paddr_t +ub1_pivget(int lev) +{ + register struct device **dp = dvub1.ubdevs; + + /* Return first device wanting attention on given level. */ + for (; *dp; dp++) + if (lev == (*dp)->dv_pireq) + return (paddr_t)(*(*dp)->dv_pivec)(*dp); + return 0; +} + +paddr_t +ub3_pivget(int lev) +{ + register struct device **dp = dvub3.ubdevs; + + /* Return first device wanting attention on given level. */ + for (; *dp; dp++) + if (lev == (*dp)->dv_pireq) + return (paddr_t)(*(*dp)->dv_pivec)(*dp); + return 0; +} + +#endif /* KLH10_CPU_KS */ diff --git a/src/dvuba.h b/src/dvuba.h new file mode 100644 index 0000000..32f14e5 --- /dev/null +++ b/src/dvuba.h @@ -0,0 +1,149 @@ +/* DVUBA.H - Definitions for emulating KS10 Unibus Adapters +*/ +/* $Id: dvuba.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: dvuba.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ +/* +** Portions of this file were derived from AI:KSHACK;KSDEFS 193 +*/ + +#ifndef DVUBA_INCLUDED +#define DVUBA_INCLUDED 1 + +#ifdef RCSID + RCSID(dvuba_h,"$Id: dvuba.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Define types to use for holding new-IO unibus addresses and register +** values. +** Unibus addresses are 18 bits. +** Unibus device registers are only 16 bits. +** Since an "int" is guaranteed to always have at least 16 +** bits, and is normally the most computationally efficient +** type, that's what we'll use here. +** All bets are off for the peculiar non-unibus devices such as the +** memory status register (36-bit) and "unibus adapter" (32-bit). +** +*/ +#ifndef WORD10_INT18 /* Ensure uint18 is defined */ +# include "word10.h" +#endif +typedef uint18 dvuadr_t; /* Device: Unibus Address */ +typedef unsigned int dvureg_t; /* Device: Unibus Register value */ + + +#ifndef KLH10_DEVUBA_MAX +# define KLH10_DEVUBA_MAX 10 /* Max # of devs on each unibus */ +#endif + +/* In the ITS system source, +** Unibus #1 has mnemonic UBAQ (for QSK => DSK). +** #3 has mnemonic UBAI +*/ + +/* Unibus Paging RAM - one per Unibus, 0763000-763077 inclusive */ + +#define UB_UBAPAG 0763000 /* UBA Paging RAM (One per Unibus) */ +#define UBA_UBALEN 64 /* Length of UBA Paging RAM */ +#define UB_UBAEND (UB_UBAPAG+UBA_UBALEN) /* First non-valid addr */ + +/* When read: */ +#define UBA_PPAR 0020000 /* 4.5 RAM parity bit */ +#define UBA_PRPW 0010000 /* 4.4 Force read-pause-write */ +#define UBA_P16B 0004000 /* 4.3 Disable upper two bits on Unibus transfers */ +#define UBA_PFST 0002000 /* 4.2 Fast mode enable */ +#define UBA_PVAL 0001000 /* 4.1 Entry is valid */ +#define UBA_PPVL 0000400 /* 3.9 Parity is valid */ +/* +$UPPAG==:121200,, ; 3.2 - 2.2 ITS page number + ; 2.1 ITS half page + ; 3.2 - 2.1 DEC page number +*/ +/* When written: */ +#define UBA_QRPW 0400000 /* 2.9 Force read-pause-write */ +#define UBA_Q16B 0200000 /* 2.8 Disable 18-bit UB xfer (hi 2 bits) */ +#define UBA_QFST 0100000 /* 2.7 Enable Fast (36-bit) xfer mode */ +#define UBA_QVAL 0040000 /* 2.6 Entry is valid */ +#define UBA_QPAG 0003777 /* 2.2 - 1.2 ITS page number */ + /* 1.1 ITS half page */ + /* 2.2 - 1.1 DEC page number */ + +/* Unibus Status Register - one per Unibus */ + +#define UB_UBASTA 0763100 /* UBA Status Register (One per Unibus) */ +/* [R=Read, W=Write, C=Cleared by writing a 1, + *=Cleared by any write] +*/ +#define UBA_BTIM 0400000 /* 2.9 Unibus timeout [R/C] */ +#define UBA_BBAD 0200000 /* 2.8 Bad mem data (on NPR transfer) [R/C] */ +/* (Master will timeout instead if UBA_BDXF set) */ +#define UBA_BPAR 0100000 /* 2.7 KS10 bus parity error [R/C] */ +#define UBA_BNXD 0040000 /* 2.6 CPU addressed non-ex device [R/C] */ +#define UBA_BHIG 0004000 /* 2.3 Intrpt req on BR7 or BR6 (high) [R] */ +#define UBA_BLOW 0002000 /* 2.2 Intrpt req on BR5 or BR4 (low) [R] */ +#define UBA_BPWR 0001000 /* 2.1 Power low [R|*] */ +#define UBA_BDXF 0000200 /* 1.8 Disable xfr on uncorrectable data[R/W]*/ +#define UBA_BINI 0000100 /* 1.7 Issue Unibus init [W] */ +#define UBA_BPIH 0000070 /* 1.6-1.4 PI lev for BR7 or BR6 (hi) [R/W] */ +#define UBA_BPIL 0000007 /* 1.3-1.1 PI lev for BR5 or BR4 (low) [R/W] */ + +#define UB_UBAMNT 0763101 /* UBA Maintenance (One per Unibus) */ +/* 1.2 Spare maintenance bit (?) + 1.1 Change NPR address (?) +*/ + +/* Other Unibus devices */ + +/* KS10 Memory Status Register, controller 0 */ +#define UB_KSECCS 0100000 /* Memory Status Register, ctlr 0 */ + + +/* Define internal structure to emulate the above Unibii +*/ +extern struct ubctl { + h10_t ubpmap[UBA_UBALEN]; /* Contents of UBA paging RAM */ + h10_t ubsta; /* Unibus Status Register */ + int ubn; /* Unibus # (1 or 3) */ + int ubhilev; /* "High" PI level bit assignment */ + int ublolev; /* "Low" " " " " */ + int *ubpireqs; /* Address of place to put UB PI reqs */ + int ubndevs; /* # devices on bus */ + struct device *ubdevs[KLH10_DEVUBA_MAX+1]; /* Devs on bus */ + dvuadr_t ubdvadrs[KLH10_DEVUBA_MAX*2]; /* Beg and end addrs */ +} dvub1, dvub3; + +extern void dvub_init(void); +int dvub_add(FILE *, struct device *, int); + +extern uint32 /* All reads return 32 bits */ + ub_read (w10_t), /* (ioaddr) */ + ub_bread(w10_t), /* (ioaddr) */ + uba_read(struct ubctl *, dvuadr_t); /* (ub, uaddr) */ + +extern void /* All writes take 18 bits */ + ub_write(w10_t, h10_t), /* (ioaddr, val) */ + ub_bwrite(w10_t, h10_t), /* (ioaddr, val) */ + uba_write(struct ubctl *, dvuadr_t, h10_t); /* (ub, uaddr, val) */ + +extern paddr_t ub1_pivget(int); /* For PI system */ +extern paddr_t ub3_pivget(int); + +#endif /* ifndef DVUBA_INCLUDED */ diff --git a/src/enaddr.c b/src/enaddr.c new file mode 100644 index 0000000..567bc49 --- /dev/null +++ b/src/enaddr.c @@ -0,0 +1,417 @@ +/* ENADDR.C - Utility to manage ethernet interface addresses +*/ +/* $Id: enaddr.c,v 2.5 2001/11/19 10:18:34 klh Exp $ +*/ +/* Copyright © 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: enaddr.c,v $ + * Revision 2.5 2001/11/19 10:18:34 klh + * Solaris port: add dp_strerror def + * + * Revision 2.4 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* + Utility to manage ethernet interface addresses. + Allows testing osdnet.c's interface query code, and for a specific + interface allows one to read or set the MAC address, as well + as add or delete multicast addresses that the interface will recognize. + + "ifconfig" can do some but not all of these things. + + Originally intended to replace Larry Sendlosky's mini-utils, where: + + LWS prog Equivalent + -------- ------------------------- + rln0addr: enaddr ln0 Read ether addr + sln0addr: enaddr ln0 AA:0:4:0:AC:60 Set ether addr + defln0addr: enaddr ln0 default Reset ether addr (if possible) + sln0mcat: enaddr ln0 +AB:0:0:4:0:0 \ Set multicast addrs + +9:0:2B:0:0:FF \ + +AB:0:0:3:0:0 \ + +AB:0:0:1:0:0 \ + +AB:0:0:2:0:0 +*/ + +#include +#include +#include +#include + +#include "cenv.h" +#include "rcsid.h" +#include "osdnet.h" + +#ifdef RCSID + RCSID(enaddr_c,"$Id: enaddr.c,v 2.5 2001/11/19 10:18:34 klh Exp $") +#endif + +#ifndef TRUE +# define TRUE 1 +#endif +#ifndef FALSE +# define FALSE 0 +#endif + + /* Parameters */ +char *ifc; +int endef = FALSE; +char *enstr = NULL; +unsigned char pa_new[6]; + +#define MAXMCAT 16 +struct mcat { + int mcdel; + unsigned char mcaddr[6]; +}; +int nmcats = 0; +struct mcat mcat[MAXMCAT]; + +unsigned char pa_cur[6]; +unsigned char pa_def[6]; + +static char *sprinteth(char *, unsigned char *); +static void penetaddr(char *ifc, unsigned char *cur, unsigned char *def); +static int pareth(char *, unsigned char *); + +int debugf = 0; + +char usage[] = "\ +Usage: enaddr [-v] [ [default | ] [+] [-]]\n\ + -v Outputs debug and config info for all interfaces\n\ + Specific interface to read or modify\n\ + default Reset ether addr to HW default, if known\n\ + Set ether addr to this (form x:x:x:x:x:x)\n\ + + Add multicast addr (same form)\n\ + - Delete multicast addr (same form)\n"; + + +/* Error and Diagnostic logging stuff. + Set up for eventual extraction into a separate module + */ + +#if 1 /* ENADDR */ +# define LOG_EOL "\n" +# undef LOG_DP +# define LOG_PROGNAME "enaddr" +# define DP_DBGFLG debugf +#else +# define LOG_EOL "\r\n" +# define LOG_DP dp +# if KLH10_SIMP +# define LOG_PROGNAME "simp" +# elif KLH10_DEV_DPIMP +# define LOG_PROGNAME "dpimp" +# elif KLH10_DEV_DPNI20 +# define LOG_PROGNAME "dpni20" +# endif +#endif + +#if 1 /* Error and diagnostic stuff */ + +#if CENV_SYSF_STRERROR +# include /* For strerror() */ +#endif + +/* Error and diagnostic output */ + +static const char *log_progname = LOG_PROGNAME; + +char *log_strerror(err) +{ + if (err == -1 && errno != err) + return log_strerror(errno); +#if CENV_SYSF_STRERROR + return strerror(err); +#else +# if CENV_SYS_UNIX + { +# if !CENV_SYS_XBSD /* Already in signal.h */ + extern int sys_nerr; + extern char *sys_errlist[]; +# endif + if (0 < err && err <= sys_nerr) + return sys_errlist[err]; + } +# endif + if (err == 0) + return "No error"; + else { + static char ebuf[30]; + sprintf(ebuf, "Unknown-error-%d", err); + return ebuf; + } +#endif /* !CENV_SYSF_STRERROR */ +} + + + +static void log(char *fmt, ...) +{ + fprintf(stderr, "[%s: ", log_progname); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fputs("]", stderr); +} + +static void logln(char *fmt, ...) +{ + fprintf(stderr, "[%s: ", log_progname); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fputs("]"LOG_EOL, stderr); +} + +static void logerror(char *fmt, ...) +{ + fprintf(stderr, LOG_EOL"[%s: ", log_progname); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fputs("]"LOG_EOL, stderr); +} + +static void logerror_ser(int num, char *fmt, ...) +{ + fprintf(stderr, LOG_EOL"[%s: ", log_progname); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fprintf(stderr, " - %s]"LOG_EOL, log_strerror(num)); +} + +static void logfatal(int num, char *fmt, ...) +{ + fprintf(stderr, LOG_EOL"[%s: Fatal error: ", log_progname); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fputs("]"LOG_EOL, stderr); + +#if defined(LOG_DP) + /* DP automatically kills any child as well. */ + dp_exit(&LOG_DP, num); +#endif +} + +static void logfatal_ser(int num, char *fmt, ...) +{ + fprintf(stderr, LOG_EOL"[%s: ", log_progname); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fprintf(stderr, " - %s]"LOG_EOL, log_strerror(errno)); + +#if defined(LOG_DP) + /* DP automatically kills any child as well. */ + dp_exit(&LOG_DP, num); +#endif +} + + +#define dp_strerror log_strerror +#define dbprint log +#define dbprintln logln +#define error logerror +#define syserr logerror_ser +#define efatal logfatal +#define esfatal logfatal_ser + +#endif /* Error and Diagnostic stuff */ + + +main(int argc, char **argv) +{ + int i; + ossock_t s; + char ebuf[32]; + + if (argc < 2) { + printf("%s", usage); + exit(1); + } + i = 1; + if (strcmp(argv[i], "-v")==0) { + debugf = TRUE; + ++i; + } + ifc = argv[i++]; /* Interface name if one */ + + if ((i < argc) /* Optional new interface addr? */ + && (argv[i][0] != '+') + && (argv[i][0] != '-')) { + enstr = argv[i]; + if (strcmp(enstr, "default") == 0) + endef = TRUE; + else if (!pareth(enstr, pa_new)) { + printf("enaddr: bad format for new %s address \"%s\" - use x:x:x:x:x:x\n", ifc, enstr); + exit(1); + } + ++i; /* Gobbled ifaddr */ + } + + /* Check for optional multicast address munging */ + for (nmcats = 0; (i < argc) && (nmcats < MAXMCAT); ++i, ++nmcats) { + switch (argv[i][0]) { + case '+': + mcat[nmcats].mcdel = FALSE; + break; + case '-': + mcat[nmcats].mcdel = TRUE; + break; + default: + printf("enaddr: bad multicast format \"%s\" - must prefix with + or -\n", argv[i]); + exit(1); + } + + if (!pareth(&argv[i][1], mcat[nmcats].mcaddr)) { + printf("enaddr: bad multicast format \"%s\" - use x:x:x:x:x:x\n", + &argv[i][1]); + exit(1); + } + } + + /* First, show interface info if desired */ + if (debugf) { + osn_iftab_init(IFTAB_ALL); + } + + /* Now mung interface if one given */ + if (ifc) { + /* Open socket to generic network interface */ + if (!osn_ifsock(ifc, &s)) { + perror("no socket"); + exit(1); + } + + /* Read the default and current MAC address */ + (void) osn_ifeaget(s, ifc, pa_cur, pa_def); + + /* Print the MAC addresses */ + penetaddr(ifc, pa_cur, pa_def); + + if (enstr) { + printf("Setting interface %s address from %s to %s\n", + ifc, sprinteth(ebuf, pa_cur), enstr); + + /* Setup the new MAC address - use default or new */ + (void) osn_ifeaset(s, ifc, (endef ? pa_def : pa_new)); + + /* Read back to confirm */ + (void) osn_ifeaget(s, ifc, pa_cur, pa_def); + penetaddr(ifc, pa_cur, pa_def); + } + + /* Now check for multicast munging */ + for (i = 0; i < nmcats; ++i) { + printf("%s multicast addr %s", + (mcat[i].mcdel ? " Deleting" : " Adding"), + sprinteth(ebuf, mcat[i].mcaddr)); + + if (!osn_ifmcset(s, ifc, mcat[i].mcdel, mcat[i].mcaddr)) { + printf(" ... failed: %s", log_strerror(errno)); + /* Continue anyway. Note that delete can fail harmlessly + if mcat address is already gone. + */ + } + printf("\n"); + } + + osn_ifclose(s); + } +} + +static char *sprinteth(char *cp, unsigned char *ea) +{ + sprintf(cp, "%x:%x:%x:%x:%x:%x", ea[0], ea[1], ea[2], ea[3], ea[4], ea[5]); + return cp; +} + +static void penetaddr(char *ifc, + unsigned char *cur, + unsigned char *def) +{ + char ebuf[32]; + + if (memcmp(def, "\0\0\0\0\0\0", ETHER_ADRSIZ)==0) + printf(" %s default address is unknown\n", ifc); + else + printf(" %s default address is %s\n", ifc, sprinteth(ebuf, def)); + printf(" %s current address is %s\n", ifc, sprinteth(ebuf, cur)); +} + + +static int pareth(char *cp, unsigned char *adr) +{ + unsigned int b1, b2, b3, b4, b5, b6; + int cnt; + + cnt = sscanf(cp, "%x:%x:%x:%x:%x:%x", &b1, &b2, &b3, &b4, &b5, &b6); + if (cnt != 6) { + /* Later try as single large address #? */ + return FALSE; + } + if (b1 > 255 || b2 > 255 || b3 > 255 || b4 > 255 || b5 > 255 || b6 > 255) + return FALSE; + *adr++ = b1; + *adr++ = b2; + *adr++ = b3; + *adr++ = b4; + *adr++ = b5; + *adr = b6; + return TRUE; +} + +/* Include OSDNET code, faking out unneeded packetfilter inits + * XXX: clean this up in future OSDNET. + */ +struct fakepf { int foo; }; +#define OSN_PFSTRUCT fakepf + +struct OSN_PFSTRUCT * +pfbuild(void *arg, struct in_addr *ipa) +{ + return NULL; +} +struct OSN_PFSTRUCT * +pfeabuild(void *arg, unsigned char *ea) +{ + return NULL; +} + +#include "osdnet.c" + diff --git a/src/feload.c b/src/feload.c new file mode 100644 index 0000000..eb0c6a8 --- /dev/null +++ b/src/feload.c @@ -0,0 +1,859 @@ +/* FELOAD.C - PDP-10 boot loader routines +*/ +/* $Id: feload.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: feload.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* Loads executables into physical memory. +*/ + +#include +#include /* Malloc and friends */ +#include +#include +#include + +#include "klh10.h" /* For overall config defs */ +#include "word10.h" +#include "kn10ops.h" +#include "wfio.h" +#include "feload.h" + +#ifdef RCSID + RCSID(feload_c,"$Id: feload.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Commonly needed vector value */ +#define LH_JRST ((h10_t)I_JRST<<9) + +/* DEC sharable save format - block IDs */ +#define DECSSF_DIR 01776 +#define DECSSF_EV 01775 +#define DECSSF_PDV 01774 +#define DECSSF_END 01777 + +static int load_typefind(WFILE *, struct loadinfo *); +static int load_sblk(WFILE *, struct loadinfo *); +static int dump_dsblk(WFILE *, struct loadinfo *, int); +static int load_decsav(WFILE *, struct loadinfo *); +static int load_decexe(WFILE *, struct loadinfo *); + +int +fe_load(register WFILE *wf, + register struct loadinfo *lp) +{ + /* Initialize result variables of loadinfo struct */ + lp->ldi_allerr = 0; + lp->ldi_loaddr = (uint18)1 << 18; + lp->ldi_hiaddr = 0; + LRHSET(lp->ldi_startwd, 0, 0); + lp->ldi_ndata = 0; + lp->ldi_nsyms = 0; + lp->ldi_cksumerr = 0; + lp->ldi_aibgot = 0; + lp->ldi_evlen = -1; + lp->ldi_evloc = 0; + + /* See if load format is already known. If not, try to determine + ** from initial contents of file. + */ + if (lp->ldi_type == LOADT_UNKNOWN) { + lp->ldi_type = load_typefind(wf, lp); + wf_rewind(wf); /* Back up to start */ + } + + switch (lp->ldi_type) { + case LOADT_UNKNOWN: + lp->ldi_typname = "Unknown"; + break; + case LOADT_PDUMP: + lp->ldi_typname = "ITS-PDUMP"; + fprintf(stderr, "Loading aborted, unable to load ITS PDUMP format.\n"); + break; + case LOADT_SBLK: + lp->ldi_typname = "ITS-SBLK"; + return load_sblk(wf, lp); + case LOADT_DECSAV: + lp->ldi_typname = "DEC-CSAV"; + return load_decsav(wf, lp); + case LOADT_DECEXE: + lp->ldi_typname = "DEC-PEXE"; + return load_decexe(wf, lp); + } + return 0; +} + +/* LDVM_MAP - Do loader-specific mapping of addresses to physical memory +** locations. For now, assumes only phys mapping in effect, no virtual +** funnies. +** Also, permit addresses larger than 18 bits, so caller must be sure +** arg is OK! +*/ +static +vmptr_t ldvm_map(paddr_t pa) +{ + if (pa <= AC_17) + return &cpu.acblk.cur[pa]; /* Use currently active AC block */ + + return vm_physmap(pa); +} + +/* FE_DUMP - Dump out memory in a loadable format. +** The loadinfo struct must be properly set up beforehand: +** ldi_type = desired dump format +** ldi_loaddr, ldi_hiaddr = range to dump +** ldi_startwd = start address in RH if any +** (other members may become significant with other formats) +*/ +int +fe_dump(register WFILE *wf, register struct loadinfo *lp) +{ + int res; + + /* Initialize result variables of loadinfo struct */ + lp->ldi_allerr = 0; + lp->ldi_ndata = 0; + lp->ldi_nsyms = 0; + lp->ldi_cksumerr = 0; + + /* See if load format is already known. If not, try to determine + ** from current system type. + */ + if (lp->ldi_type == LOADT_UNKNOWN) { +#if KLH10_SYS_ITS + lp->ldi_type = LOADT_SBLK; +#else + lp->ldi_type = LOADT_DECSAV; +#endif + } + + switch (lp->ldi_type) { + case LOADT_SBLK: + case LOADT_DECSAV: + lp->ldi_typname = (lp->ldi_type == LOADT_SBLK) + ? "ITS-SBLK" : "DEC-CSAV"; + res = dump_dsblk(wf, lp, lp->ldi_type); + break; + + default: + fprintf(stderr, "fe_dump: unknown format %d\n", lp->ldi_type); + lp->ldi_allerr++; + return 0; + } + + wf_flush(wf); + return res; +} + + +static int +load_typefind(register WFILE *wf, + struct loadinfo *lp) +{ + w10_t w, w2; + + /* Read first word (or two) to help determine format type */ + if (wf_get(wf, &w) <= 0) { + fprintf(stderr, "Loading aborted, initial read failed\n"); + return LOADT_UNKNOWN; /* Ugh return */ + } + + if (op10m_skipge(w)) { + /* First word is positive */ + if (op10m_skipe(w)) /* If 1st word is 0 */ + return LOADT_PDUMP; /* assume ITS PDUMP */ + if (LHGET(w) == DECSSF_DIR) /* If special EXE value */ + return LOADT_DECEXE; /* assume DEC sharable */ + return LOADT_SBLK; /* Else must be SBLK with no RIM */ + } + + /* First word is negative, check for special values */ + if (RHGET(w)) /* If RH non-zero, */ + return LOADT_DECSAV; /* can't be any of the others */ + + if (LHGET(w) != 0710440 /* If not DATAI PTR,0 (RIM) */ + && LHGET(w) != 0777761) /* or -17,,0 (RIM10) */ + return LOADT_DECSAV; /* then also can't be RIM/SBLK */ + + /* We might be looking at a RIM or RIM10 preface to a SBLK file. + ** Check the second word to be doubly sure. If it isn't a CONO PTR,60 + ** then give up and default to DEC unsharable format. + */ + if (wf_get(wf, &w2) <= 0) { + fprintf(stderr, "Loading aborted, read of 2nd word failed\n"); + return LOADT_UNKNOWN; /* Ugh return */ + } + if (LHGET(w2) == 0710600 && RHGET(w2) == 060) /* CONO PTR,60 */ + return LOADT_SBLK; + return LOADT_DECSAV; +} + +/* ITS SBLK format loading code */ + +/* +SBLK format: + + + + [] + + + : + This block contains 1 to 16 (RIM10) or more (RIM) words, where the + first word must be non-zero and the last word (which may be the + first word, and which marks the end of the block) is JRST 1. + The RIM (Read-In Mode) loader starts with: + 0: 710440,, 0 DATAI PTR, + 1: 710600,, 60 CONO PTR,60 + The RIM10 loader starts with: + 0: -017,,0 + 1: CONO PTR,60 + + : + -<# wds>,, + + + + : + A non-negative word seen while looking for a simple block is + interpreted as the start address. (normally, a JRST ). + This may be 0 if there is no start address. + + : + Exactly like simple blocks, but with zero s. A duplicate + start-address indicates there are no more symtab blocks. +*/ + +#define DDTLOC 0774000 /* Loc of ITS Exec DDT in phys mem */ + + +static int ld_bget(WFILE *, struct loadinfo *, w10_t *); + +static int +load_sblk(register WFILE *wf, + register struct loadinfo *lp) +{ + register int i; + register paddr_t addr; + register w10_t *wp; +#define WBUFLEN 16000+2 /* Make room for big blocks (ITS symtab) */ + register w10_t *wbuf; + + /* Get a buffer for reading blocks into. This is done with malloc + ** instead of using the stack because at least one platform (Mac MPW C) + ** silently generates incorrect code for stack frames > 32Kb. + */ + wbuf = (w10_t *)malloc(sizeof(w10_t) * WBUFLEN); + if (!wbuf) { + fprintf(stderr, "Loader couldn't allocate buffer - malloc failed.\n"); + return 0; + } + + /* First skip over RIM10 loader */ + for (;;) { + if (wf_get(wf, wbuf) <= 0) { + fprintf(stderr, "SBLK load aborted before data blocks\n"); + free(wbuf); + return 0; /* Ugh return */ + } + if ((LHGET(wbuf[0]) == LH_JRST) && (RHGET(wbuf[0]) == 1)) + break; + } + if (lp->ldi_debug) + printf(" RIM10 Loader skipped.\n"); + + + /* Now read simple data blocks and store them in + ** PDP-10 physical memory. This could be done with a memcpy if + ** phys mem is identical to the buffer format. + */ + while ((i = ld_bget(wf, lp, wbuf)) > 1) { + addr = RHGET(wbuf[0]); /* Find addr of first word */ + if (addr < lp->ldi_loaddr) lp->ldi_loaddr = addr; + lp->ldi_ndata += (i -= 2); + for (wp = wbuf+1; --i >= 0; ++wp, ++addr) + vm_pset(ldvm_map(addr & H10MASK), *wp); + if (addr > lp->ldi_hiaddr) lp->ldi_hiaddr = addr-1; + } + if (i <= 0) { + fprintf(stderr, "SBLK aborted before first start address\n"); + free(wbuf); + return 0; + } + + /* Now have a positive word in wbuf, assume it's the start addr */ + lp->ldi_startwd = wbuf[0]; /* Remember it */ + if (lp->ldi_debug) + printf(" Start address = %lo\n", (long)RHGET(wbuf[0])); + + /* Now gobble up symtab blocks. */ + while ((i = ld_bget(wf, lp, wbuf)) > 1) { + register w10_t sptr; + int nsym = i - 2; + lp->ldi_nsyms += nsym; + addr = RHGET(wbuf[0]); /* Find addr of first word */ + if (addr) { + int j; + + if (addr != 3) { /* Understand Misc Info blocks */ + fprintf(stderr, "Symtab block with nonzero RH: %#lo\n", + (long)addr); + continue; /* Ignore it */ + } + if (i <= 0) + continue; + + /* We only understand Type 1 subblocks (Assembly Info) */ + if (LHGET(wbuf[1]) != ((-6)&H10MASK) || RHGET(wbuf[1]) != 1) { + fprintf(stderr, "Unknown subblock in MiscInfo blk: %lo,,%lo\n", + (long)LHGET(wbuf[1]), (long)RHGET(wbuf[1])); + continue; /* Ignore it */ + } + if (--i <= 0) continue; + + /* Have at least one word of data for info. + ** Copy them into array... unspecified words remain zero. + */ + lp->ldi_aibgot = i = ((i > 6) ? 6 : i); + for (j = 0; j < i; ++j) + lp->ldi_asminf[j] = wbuf[j+2]; + continue; /* And that's all for now */ + } + if (nsym <= 0) continue; + + /* Add this batch of syms to Exec DDT's symtab. + ** Perhaps add a flag to make this optional. + */ + sptr = vm_pget(ldvm_map((paddr_t)DDTLOC-1)); + if (RHGET(sptr) != DDTLOC-2) { /* Check for DDT */ + printf("No EXEC DDT? Ignoring block of %d syms.\n", nsym); + continue; /* Not there */ + } + sptr = vm_pget(ldvm_map((paddr_t)DDTLOC-2)); + LHSET(sptr, (LHGET(sptr)-nsym)&H10MASK); + RHSET(sptr, (RHGET(sptr)-nsym)&H10MASK); + addr = RHGET(sptr); /* Copy sym data to this loc */ + for (wp = wbuf+1; --i >= 0; ++wp, ++addr) + vm_pset(ldvm_map(addr & H10MASK), *wp); + + /* Now tell DDT about it */ + vm_pset(ldvm_map((paddr_t)DDTLOC-2), sptr); /* Store new symtab ptr */ + vm_psetlh(ldvm_map((paddr_t)DDTLOC-1), H10MASK); /* Tell DDT symtab munged */ + vm_pset(ldvm_map((paddr_t)DDTLOC-4), lp->ldi_startwd); /* Set start addr */ + printf("Added %d syms to DDT, total %ld\n", nsym, + (long) -(LHGET(sptr)|~MASK18)); + } + + if (i <= 0) { + fprintf(stderr, "SBLK aborted before final start address\n"); + free(wbuf); + return 0; + } + + /* Last check -- should be duplicate of first start word */ + if (LHGET(wbuf[0]) != LHGET(lp->ldi_startwd) + || RHGET(wbuf[0]) != RHGET(lp->ldi_startwd)) { + fprintf(stderr, "SBLK start address mismatch: %#lo,,%#lo != %#lo,,%#lo\n", + (long) LHGET(lp->ldi_startwd), (long) RHGET(lp->ldi_startwd), + (long) LHGET(wbuf[0]), (long) RHGET(wbuf[0])); + free(wbuf); + return 0; + } + free(wbuf); + return 1; /* Won! */ +} + +/* Read in simple block of up to WBUFLEN words, including header & checksum. +** Returns positive # words read, if all's well. +** Returns # <= 0 if error, where # gives words read so far. +*/ +static int +ld_bget(register WFILE *wf, + register struct loadinfo *lp, + register w10_t *aw) +{ + register int i, cnt; + + /* Get first word */ + if (wf_get(wf, aw) <= 0) + return 0; + + if (op10m_skipge(*aw)) + return 1; /* Not a simple block, stop now */ + + /* Simple block, read in rest of words! */ + i = -(LHGET(*aw) | -H10SIGN); /* Extend sign and negate to get pos */ + if (i > (WBUFLEN-2)) { + fprintf(stderr, "Block size too large: %d (max %d)\n", i, WBUFLEN-2); + return -1; + } + + if (lp->ldi_debug) + printf(" ------ Starting block of %d. words, addr=%lo ------\n", + i, (long)RHGET(*aw)); + + for (cnt = 1; --i >= 0; ++cnt) { + if (wf_get(wf, ++aw) <= 0) { + fprintf(stderr, "Unexpected EOF while reading block.\n"); + return -cnt; + } + /* Could compute checksum here */ + } + + /* Block data read in, now get trailing checksum */ + if (wf_get(wf, ++aw) <= 0) { + fprintf(stderr, "Unexpected EOF while reading checksum.\n"); + return -cnt; + } + + /* Could compare checksum here */ + + return cnt+1; +} + +/* ITS SBLK (and DEC SAV) dumping code +** Per definition of SBLK, no zero words are ever dumped. +*/ +static int sblk_out(WFILE *, paddr_t, int, w10_t *); +static int dsav_out(WFILE *, paddr_t, int, w10_t *); + +static int +dump_dsblk(register WFILE *wf, + register struct loadinfo *lp, + int typ) /* A LOADT_xxx type */ +{ + register w10_t *wp; + register paddr_t addr; + register int blen; /* Block length */ + paddr_t baddr; /* Block start addr */ +#define WBOLEN 128 + w10_t wbuf[WBOLEN]; + + if (typ == LOADT_SBLK) { + LRHSET(wbuf[0], LH_JRST, 1); /* First word is JRST 1 */ + if (wf_put(wf, wbuf[0]) <= 0) + return 0; + } + + addr = lp->ldi_loaddr; + for (; addr <= lp->ldi_hiaddr;) { + blen = 0; + wp = wbuf; + + /* Scan for nonzero word */ + for (; addr <= lp->ldi_hiaddr; ++addr) { + *wp = vm_pget(ldvm_map(addr)); + if (op10m_skipn(*wp)) { + blen = 1; + baddr = addr; + break; + } + } + if (!blen) break; + + /* Have 1st wd in buffer. + ** Now scan for first zero word, or until buffer full + */ + for (; ++addr <= lp->ldi_hiaddr;) { + if (blen >= WBOLEN) + break; + *++wp = vm_pget(ldvm_map(addr)); + if (op10m_skipe(*wp)) + break; + ++blen; + } + + /* Block done. addr points to next word (may be zero), + ** blen has # of words in buffer/block. + */ + if ( (typ == LOADT_SBLK) ? (sblk_out(wf, baddr, blen, wbuf) <= 0) + : ((typ == LOADT_DECSAV) ? (dsav_out(wf, baddr, blen, wbuf) <= 0) + : 1)) { + return 0; /* Failure of some kind */ + } + + lp->ldi_ndata += blen; /* Remember # words */ + } + + /* Data blocks done, now add start address (or entry vector) */ + if (typ == LOADT_SBLK) { + + /* Build start address to mark end of simple blocks */ + LRHSET(wbuf[0], LH_JRST, RHGET(lp->ldi_startwd)); + wf_put(wf, wbuf[0]); + + /* Symbols should go here, if we ever remember them */ + + /* Now use duplicate start address to mark end of symbols */ + LRHSET(wbuf[0], LH_JRST, RHGET(lp->ldi_startwd)); + return wf_put(wf, wbuf[0]); + + } else if (typ == LOADT_DECSAV) { + + /* Build entry vector to mark end of blocks */ + switch (lp->ldi_evlen) { + case -1: + case 0: + case LH_JRST: + LRHSET(wbuf[0], LH_JRST, RHGET(lp->ldi_startwd)); + break; + default: + LRHSET(wbuf[0], lp->ldi_evlen, lp->ldi_evloc); + } + return wf_put(wf, wbuf[0]); + } + return 0; +} + +static int +sblk_out(register WFILE *wf, + register paddr_t addr, + register int len, + register w10_t *wp) +{ + register w10_t w; + + LRHSET(w, (-len)&H10MASK, addr & H10MASK); /* -,, */ + if (wf_put(wf, w) <= 0) + return 0; + for (; --len >= 0; ++wp) + if (wf_put(wf, *wp) <= 0) + return 0; + + LRHSET(w, 0, 0); /* Bogus checksum for now */ + return wf_put(wf, w); +} + +/* DEC SAV non-sharable SAVE format loading code */ + +/* +DEC nonsharable save format: + + + + : + -<# wds>,, + + + : + ,, + + Vector word 0 is instr to execute to start program. + Vector word 1 is instr to execute to reenter program. + Vector word 2 contains program version # info. + + BUT if LH is 254000 then: + start addr = RH(120) + reenter addr = RH(124) + version info in 137 + Actually it appears that the RH may be the start address. + If it's non-zero, let's use that. +*/ + +static int +load_decsav(register WFILE *wf, + register struct loadinfo *lp) +{ + register paddr_t addr; + register int32 cnt; + w10_t wdata; + + lp->ldi_evlen = -1; /* Init entry vector length in case fail */ + + for (;;) { + /* Read first word of block, should be an IOWD */ + if ((cnt = wf_get(wf, &wdata)) <= 0) { + return cnt ? 0 : 1; + } + if (op10m_skipge(wdata)) /* If word is positive, done! */ + break; + + /* Read in and load data words for one block */ + cnt = -(LHGET(wdata) | ~MASK18); /* Get positive count */ + addr = RHGET(wdata)+1; /* Find 1st loc to load into */ + if (lp->ldi_debug) + printf(" ------ Starting block of %ld. words, addr=%lo ------\n", + (long)cnt, (long)addr); + + if (addr < lp->ldi_loaddr) lp->ldi_loaddr = addr; + lp->ldi_ndata += cnt; + for (; --cnt >= 0; ++addr) { + if (wf_get(wf, &wdata) <= 0) { + fprintf(stderr, "Loading aborted, read failed\n"); + return 0; + } + vm_pset(ldvm_map(addr & H10MASK), wdata); + } + if (addr > lp->ldi_hiaddr) lp->ldi_hiaddr = addr-1; + } + + /* Positive header word seen, assume entry vector */ + if (lp->ldi_debug) + printf(" ------ Entry vector word %#lo,,%lo ------\n", + (long)LHGET(wdata), (long)RHGET(wdata)); + lp->ldi_evlen = LHGET(wdata); + lp->ldi_evloc = RHGET(wdata); + + addr = lp->ldi_evloc; /* Set up probable start addr */ + if ((lp->ldi_evlen == LH_JRST) && !addr) + addr = vm_pgetrh(ldvm_map((paddr_t)0120)); + LRHSET(lp->ldi_startwd, 0, addr & H10MASK); + + return 1; +} + +/* DEC SAV dumping code +** Similar to SBLK but a little simpler. +*/ +static int +dsav_out(register WFILE *wf, + register paddr_t addr, + register int len, + register w10_t *wp) +{ + register w10_t w; + + LRHSET(w, (-len)&H10MASK, (addr-1) & H10MASK); /* -,, */ + if (wf_put(wf, w) <= 0) + return 0; + for (; --len >= 0; ++wp) + if (wf_put(wf, *wp) <= 0) + return 0; + + /* No checksum word in DEC SAV format */ + return 1; +} + +/* DEC EXE sharable SAVE format loading code */ + +/* +DEC sharable SAVE format: + : + + + + + : + data pages + +Each block has this general format: + + ,,<# words in block, including this word> + <#-1 remaining words> + + : + 1776,,<#> + : + <27-bit page # in file, 0 if none> + <9-bit repeat cnt><27-bit page # in process> + + Access bits: + B1 - pages are sharable + B2 - pages are writable + Repeat count: # of pages (minus 1) in group. + + : ; Optional in TOPS-10 + 1775,,3 + <# words in entry vector> + + + : ; optional, basically ignore this + 1774,,<#> + <#-1 words> + + : + 1777,,1 + +Entry vector contents are the same as for SAV (non-sharable) format. + +TOPS-10 EXEs appear to leave out the entry vector block; in that case, +the contents are taken from + start addr = RH(120) + reenter addr = RH(124) + version info in 137 +Again, just as for SAV format. + +*/ + +#define DEC_MAXPHYSPGS ((paddr_t)1<<(PAG_PABITS-9)) /* # DEC pages on machine */ + +static void decld_clrpag(paddr_t); +static int decld_rdpag(struct wfile *, paddr_t, paddr_t, int); + + +static int +load_decexe(register WFILE *wf, + register struct loadinfo *lp) +{ + register paddr_t addr; + register int32 cnt; + register int i; + w10_t wdata; + register w10_t *wp; +#define DBUFLEN (1+(512*3)) /* Make room for directory area blocks */ + w10_t wbuf[DBUFLEN]; /* For loading block data */ + + lp->ldi_evlen = -1; /* Init entry vector length in case fail */ + + wp = wbuf; + if (wf_get(wf, wp) <= 0) { + fprintf(stderr, "Loading aborted, first read failed\n"); + return 0; + } + if (LHGET(*wp) != DECSSF_DIR) { + fprintf(stderr, "1st word not directory section: %#lo\n", + (long)LHGET(*wp)); + return 0; + } + if ((cnt = RHGET(*wp)-1) > DBUFLEN) { + fprintf(stderr, "Directory section too large: %ld words\n", + (long)cnt+1); + return 0; + } + if (cnt & 01) + fprintf(stderr, "Warning, dir block has non-pair word count: %ld\n", + (long)cnt+1); + + /* Read in directory section */ + if (lp->ldi_debug) + printf(" DIR section = %#lo wds\n", (long)cnt); + for (i = cnt; --i >= 0;) { + if (wf_get(wf, ++wp) <= 0) { + fprintf(stderr, "Loading aborted, read failed in dir block\n"); + return 0; + } + } + + /* Gobbled directory block, now get entry vector */ + if (wf_get(wf, &wdata) <= 0) { + fprintf(stderr, "Loading aborted, read failed after dir block\n"); + return 0; + } + + if (LHGET(wdata) != DECSSF_EV || RHGET(wdata) != 3) { + if (lp->ldi_debug) + fprintf(stderr, "No entvec block, word: %#lo,,%#lo\n", + (long) LHGET(wdata), (long) RHGET(wdata)); + lp->ldi_evlen = 0; + lp->ldi_evloc = 0; + } else { + if (wf_get(wf, &wdata) <= 0) { + fprintf(stderr, "Loading aborted, read failed in entvec block\n"); + return 0; + } + lp->ldi_evlen = RHGET(wdata); + if (wf_get(wf, &wdata) <= 0) { + fprintf(stderr, "Loading aborted, read failed in entvec block\n"); + return 0; + } + lp->ldi_evloc = ((paddr_t)LHGET(wdata) << 18) | RHGET(wdata); + + if (lp->ldi_debug) + printf(" ENTVEC section = %#lo wds at %#lo\n", + (long)lp->ldi_evlen, (long)lp->ldi_evloc); + } + + /* At this point we could also scan for the PDV and END blocks, but + ** why bother? + */ + + /* Now grovel over the directory section, loading in all the pages it + ** knows about. + ** Since we're loading physical memory, the access bits are ignored. + ** If the file page # is 0, the page group has its memory cleared; this may + ** or may not be what the DEC bootstrap does, but seems useful. + */ + for (wp = wbuf+1; cnt > 0; cnt -= 2, wp += 2) { + register paddr_t fpag = RHGET(wp[0]); + register paddr_t ppag = RHGET(wp[1]); + i = 1 + ((LHGET(wp[1]) >> 9) & 0777); /* High 9 bits are rpt cnt */ + if (!fpag) { + for (; --i >= 0; ++ppag) { + if (lp->ldi_debug) + printf(" Page %#lo: clear\n", (long)ppag); + decld_clrpag(ppag); + } + } else { + if (lp->ldi_debug) + printf(" Page %#lo: file page %#lo (n=%d.)\n", + (long)fpag, (long)ppag, i); + decld_rdpag(wf, fpag, ppag, i); + } + } + + addr = lp->ldi_evloc; /* Set up probable start addr */ + if (!addr && ( (lp->ldi_evlen == LH_JRST) /* Old vector? */ + || (lp->ldi_evlen == 0))) { /* or no vector? */ + addr = vm_pgetrh(ldvm_map((paddr_t)0120)); /* Use .JBSA */ + } + + LRHSET(lp->ldi_startwd, 0, addr & H10MASK); + return 1; +} + +static void +decld_clrpag(register paddr_t pag) +{ + register int i = 512; + register w10_t wz; + + if (pag >= DEC_MAXPHYSPGS) { + fprintf(stderr, "Loader warning: trying to clear non-ex page %d\n", + pag); + return; + } + pag <<= 9; + op10m_setz(wz); + for (; --i >= 0; ++pag) + vm_pset(ldvm_map(pag), wz); +} + +static int +decld_rdpag(register struct wfile *wf, + paddr_t fpag, + paddr_t ppag, + int pcnt) +{ + register paddr_t addr; + register int i; + w10_t w; + + if (!wf_seek(wf, (long)fpag<<9)) { + return 0; + } + addr = ppag << 9; + for (; --pcnt >= 0; ++ppag, ++fpag) { + if (ppag >= DEC_MAXPHYSPGS) { + fprintf(stderr, "Loading aborted, trying to load non-ex page %d\n", + ppag); + return 0; + } + for (i = 512; --i >= 0; ++addr) { + if (wf_get(wf, &w) <= 0) { + fprintf(stderr, "Loading aborted, read failed for file page %d, proc page %d\n", + fpag, ppag); + return 0; + } + vm_pset(ldvm_map(addr), w); + } + } + return 1; +} diff --git a/src/feload.h b/src/feload.h new file mode 100644 index 0000000..1f59646 --- /dev/null +++ b/src/feload.h @@ -0,0 +1,74 @@ +/* FELOAD.H - PDP-10 boot loader defs & routines +*/ +/* $Id: feload.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: feload.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#ifndef FELOAD_INCLUDED +#define FELOAD_INCLUDED 1 + +#ifdef RCSID + RCSID(feload_h,"$Id: feload.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +#include "klh10.h" +#include "word10.h" /* For w10_t etc */ +#include "kn10def.h" /* For vaddr_t */ +#include "wfio.h" /* For WFILE */ + +enum loadtypes { + LOADT_UNKNOWN, + LOADT_SBLK, + LOADT_PDUMP, + LOADT_DECSAV, + LOADT_DECEXE +}; + +struct loadinfo { + /* Argument variables for loader */ + int ldi_type; /* LOADT_xxx type (can be UNKNOWN) */ + int ldi_debug; /* TRUE to print debug info during load */ + + /* Remaining are all result variables from loading */ + char *ldi_typname; /* String for LOADT_xxx type */ + int ldi_allerr; + paddr_t ldi_loaddr, ldi_hiaddr; + w10_t ldi_startwd; + int ldi_ndata, ldi_nsyms; + int ldi_cksumerr; + int ldi_aibgot; /* If non-zero, # wds in assembly info block */ + w10_t ldi_asminf[6]; /* MIDAS assembly info block: */ +#define AIB_UNAME 0 /* UNAME of person assembling */ +#define AIB_TIME 1 /* ITS disk fmt time of assembly */ +#define AIB_DEV 2 /* Device of source */ +#define AIB_FN1 3 /* FN1 of source */ +#define AIB_FN2 4 /* FN2 of source */ +#define AIB_DIR 5 /* SNAME of source */ + + paddr_t ldi_evlen; /* DEC: Entry vector length */ + paddr_t ldi_evloc; /* DEC: Entry vector location */ +}; + +/* Routines */ +extern int fe_load(WFILE *wf, struct loadinfo *lp); +extern int fe_dump(WFILE *wf, struct loadinfo *lp); + +#endif /* ifndef FELOAD_INCLUDED */ diff --git a/src/inblsh.c b/src/inblsh.c new file mode 100644 index 0000000..374d56b --- /dev/null +++ b/src/inblsh.c @@ -0,0 +1,166 @@ +/* INBLSH.C - Boolean and Shift instruction routines +*/ +/* $Id: inblsh.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: inblsh.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#include "klh10.h" +#include "kn10def.h" /* Machine defs */ +#include "kn10ops.h" /* PDP-10 ops */ + +#ifdef RCSID + RCSID(inblsh_c,"$Id: inblsh.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* See CODING.TXT for guidelines to coding instruction routines. */ + + +/* Boolean instructions */ + +#define boolinsdef(name, nameI, nameM, nameB) \ + insdef(name) \ + { register w10_t ar, m; \ + boolop(ac_get(ac), vm_read(e)); \ + ac_set(ac, ar); \ + return PCINC_1; \ + } \ + insdef(nameI) \ + { register w10_t ar, m; \ + boolop(ac_get(ac), (LRHSET(m,0,va_insect(e)), m)); \ + ac_set(ac, ar); \ + return PCINC_1; \ + } \ + insdef(nameM) \ + { register vmptr_t p = vm_modmap(e); \ + register w10_t ar, m; \ + boolop(ac_get(ac), vm_pget(p)); \ + vm_pset(p, ar); \ + return PCINC_1; \ + } \ + insdef(nameB) \ + { register vmptr_t p = vm_modmap(e); \ + register w10_t ar, m; \ + boolop(ac_get(ac), vm_pget(p)); \ + ac_set(ac, ar); \ + vm_pset(p, ar); \ + return PCINC_1; \ + } + +/* "boolop(a,b)" must use a and b to fetch args, and compute result into "ar", +** using scratch word "m" if needed. If both are used, "ar" must be set first +** so that the immediate forms (which pre-set "m") can win. +*/ + +#define boolop(a,b) op10m_setz(ar) /* SETZ ignores args */ +boolinsdef(i_setz, i_setzi, i_setzm, i_setzb) +#undef boolop + +#define boolop(a,b) op10m_seto(ar) /* SETO ignores args */ +boolinsdef(i_seto, i_setoi, i_setom, i_setob) +#undef boolop + +#define boolop(a,b) (ar = (a)) /* SETA */ +boolinsdef(i_seta, i_setai, i_setam, i_setab) +#undef boolop + +#define boolop(a,b) (ar = (b)) /* SETM */ +boolinsdef(i_setm, i_setmi, i_setmm, i_setmb) +#undef boolop + +#define boolop(a,b) (ar = (a), op10m_setcm(ar)) /* SETCA */ +boolinsdef(i_setca, i_setcai, i_setcam, i_setcab) +#undef boolop + +#define boolop(a,b) (ar = (b), op10m_setcm(ar)) /* SETCM */ +boolinsdef(i_setcm, i_setcmi, i_setcmm, i_setcmb) +#undef boolop + +#define boolop(a,b) (ar = (a), m = (b), op10m_and(ar,m)) /* AND */ +boolinsdef(i_and, i_andi, i_andm, i_andb) +#undef boolop + +#define boolop(a,b) (ar = (b), m = (a), op10m_andcm(ar, m)) /* ANDCA */ +boolinsdef(i_andca, i_andcai, i_andcam, i_andcab) +#undef boolop + +#define boolop(a,b) (ar = (a), m = (b), op10m_andcm(ar,m)) /* ANDCM */ +boolinsdef(i_andcm, i_andcmi, i_andcmm, i_andcmb) +#undef boolop + +#define boolop(a,b) (ar = (a), m = (b), \ + op10m_setcm(ar), op10m_setcm(m), op10m_and(ar,m)) /* ANDCB */ +boolinsdef(i_andcb, i_andcbi, i_andcbm, i_andcbb) +#undef boolop + +#define boolop(a,b) (ar = (a), m = (b), op10m_ior(ar,m)) /* IOR */ +boolinsdef(i_ior, i_iori, i_iorm, i_iorb) +#undef boolop + +#define boolop(a,b) (ar = (a), m = (b), \ + op10m_setcm(ar), op10m_ior(ar,m)) /* ORCA */ +boolinsdef(i_orca, i_orcai, i_orcam, i_orcab) +#undef boolop + +#define boolop(a,b) (ar = (a), m = (b), \ + op10m_setcm(m), op10m_ior(ar,m)) /* ORCM */ +boolinsdef(i_orcm, i_orcmi, i_orcmm, i_orcmb) +#undef boolop + +#define boolop(a,b) (ar = (a), m = (b), \ + op10m_setcm(ar), op10m_setcm(m), op10m_ior(ar,m)) /* ORCB */ +boolinsdef(i_orcb, i_orcbi, i_orcbm, i_orcbb) +#undef boolop + +#define boolop(a,b) (ar = (a), m = (b), op10m_xor(ar,m)) /* XOR */ +boolinsdef(i_xor, i_xori, i_xorm, i_xorb) +#undef boolop + +#define boolop(a,b) (ar = (a), m = (b), \ + op10m_xor(ar,m), op10m_setcm(ar)) /* EQV */ +boolinsdef(i_eqv, i_eqvi, i_eqvm, i_eqvb) +#undef boolop + +#undef boolinsdef + +/* Shift and Rotate */ + +#define shiftdef(name, op) \ + insdef(name) { \ + ac_set(ac, op(ac_get(ac), (h10_t)va_insect(e))); return PCINC_1; } + +shiftdef(i_ash, op10ash) /* May set flags */ +shiftdef(i_lsh, op10lsh) +shiftdef(i_rot, op10rot) +#undef shiftdef + +#define dshiftdef(name, op) \ + insdef(name) \ + { register dw10_t d; ac_dget(ac, d); \ + d = op(d, (h10_t)va_insect(e)); \ + ac_dset(ac, d); \ + return PCINC_1; \ + } + +dshiftdef(i_ashc, op10ashc) /* May set flags */ +dshiftdef(i_lshc, op10lshc) +dshiftdef(i_rotc, op10rotc) +dshiftdef(i_circ, op10circ) /* Special ITS instruction */ +#undef dshiftdef diff --git a/src/inbyte.c b/src/inbyte.c new file mode 100644 index 0000000..bf80216 --- /dev/null +++ b/src/inbyte.c @@ -0,0 +1,760 @@ +/* INBYTE.C - Byte instruction routines +*/ +/* $Id: inbyte.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: inbyte.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#include "klh10.h" +#include "kn10def.h" +#include "kn10ops.h" + +#ifdef RCSID + RCSID(inbyte_c,"$Id: inbyte.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* See CODING.TXT for guidelines to coding instruction routines. */ + +/* Note special page map context references for byte instructions. +** These are so PXCT can work by twiddling the context pointers. +** For: +** computing E - normal ea_calc() using XEA map +** read/write of byte ptr at c(E) - normal XRW map +** computing E of byte pointed to - special XBEA map +** read/write of byte data - special XBRW map +** +** Extended KL PXCT notes: +** No preprocessing is needed for EA computation because the default +** section # is always derived from the section the BP was fetched from, +** even under PXCT. However, postprocessing is needed, hence every BP +** EA calc must check to see whether PXCT is in effect or not. There +** seems to be no way of easily avoiding this check. +*/ + +/* More Extended KL notes: + +* The PRM is unclear on the topic of how a byte pointer + should be incremented when the I (indirect) field is set. X can be + treated as an invariant (except in the KA10 where ++Y will overflow). + [Assume appropriately-sized Y (either 18 or 30 bits) is used, + with overflow wrapping around within Y. If indirect happened + to be set, tough shit. + Empirical testing on KL confirms this] + +* What happens if any of the "reserved" (I,X) bits are set in a 2-word BP? + [Ignored for now.] + +* For IDPB and ILDB, which address is used for the byte reference -- one + computed after incrementing the BP (thus equal to IPB+DPB), or one + using the address derived from the original pointer (possibly incremented + by 1)? + [Assume former, which I think is better since it + ensures reproducibility; also if increment produces a faulty pointer, + will catch it quicker. However, must take care in setting PCF_FPD. + KS10 ucode appears to likewise use former method. + Empirical testing on KL also confirms former. Note in particular + that any illegal indirection checks on the 2nd wd of a two-word BP + are done AFTER the BP has been incremented! + However, note KA10 caveat (footnote on PRM p.2-87) which implies KA + uses the latter method!] + +* The PRM claims that one-word global BPs are only legal in non-zero sections. + More recent info (Uhler) indicates they are now legal in all sections, altho + two-word BPs are still only valid in non-zero section contexts. + SO: Can OWGBPs in section 0 use global addresses and access memory in other + sections? + [Assuming yes.] + +* The PRM p.2-86 is not clear on whether two-word BPs always use the EFIW + format for the second word. The format illustrated is EFIW, but the + text states "the second word can be local or global, direct or indirect". + Uhler p.7 says "the second word is either an IFIW or an EFIW". + [Assuming either IFIW or EFIW allowed] + +* Whether the context is NZ-section (thus, whether TWGs are legal) is + always determined by the section # that the first word of the BP was + fetched from. This is true even under PXCT! + +*/ + +/* Full-word byte masks, indexed by # bits in right-justified byte */ +w10_t wbytemask[64] = { +# define smask(n) (((uint18)1<<(n))-1) +# define WBMLO(n) W10XWD(0,smask(n)) +# define WBMHI(n) W10XWD(smask((n)-18),H10MASK) +# define WBMFF(n) W10XWD(H10MASK,H10MASK) + + WBMLO(0), WBMLO(1), WBMLO(2), WBMLO(3), WBMLO(4), WBMLO(5), + WBMLO(6), WBMLO(7), WBMLO(8), WBMLO(9), WBMLO(10), WBMLO(11), + WBMLO(12), WBMLO(13), WBMLO(14), WBMLO(15), WBMLO(16), WBMLO(17), + + WBMHI(18), WBMHI(19), WBMHI(20), WBMHI(21), WBMHI(22), WBMHI(23), + WBMHI(24), WBMHI(25), WBMHI(26), WBMHI(27), WBMHI(28), WBMHI(29), + WBMHI(30), WBMHI(31), WBMHI(32), WBMHI(33), WBMHI(34), WBMHI(35), + + WBMFF(36), WBMFF(37), WBMFF(38), WBMFF(39), WBMFF(40), WBMFF(41), + WBMFF(42), WBMFF(43), WBMFF(44), WBMFF(45), WBMFF(46), WBMFF(47), + WBMFF(48), WBMFF(49), WBMFF(50), WBMFF(51), WBMFF(52), WBMFF(53), + WBMFF(54), WBMFF(55), WBMFF(56), WBMFF(57), WBMFF(58), WBMFF(59), + WBMFF(60), WBMFF(61), WBMFF(62), WBMFF(63) +}; + +#if KLH10_EXTADR + +#define BPF_2WD 040 /* LH flag that indicates 2-wd BP if in NZ sect */ + +/* Table of positions and sizes for OWGBPs +** Ordered by P&S value from 037-077 inclusive. +*/ +struct owgbpe { + int pands; /* P&S value for this entry */ + int pnext; /* Next P&S if incremented */ + int p; /* P for this P&S */ + int s; /* S for this P&S */ + int bpw; /* # Bytes per word for this S */ + int btl; /* # Bytes to left for this P&S */ +} owgbptab[] = { + {37, 38, 36, 6, 6, 0}, + {38, 39, 30, 6, 6, 1}, + {39, 40, 24, 6, 6, 2}, + {40, 41, 18, 6, 6, 3}, + {41, 42, 12, 6, 6, 4}, + {42, 43, 6, 6, 6, 5}, + {43, 38, 0, 6, 6, 6}, + + {44, 45, 36, 8, 4, 0}, + {45, 46, 28, 8, 4, 1}, + {46, 47, 20, 8, 4, 2}, + {47, 48, 12, 8, 4, 3}, + {48, 45, 4, 8, 4, 4}, + + {49, 50, 36, 7, 5, 0}, + {50, 51, 29, 7, 5, 1}, + {51, 52, 22, 7, 5, 2}, + {52, 53, 15, 7, 5, 3}, + {53, 54, 8, 7, 5, 4}, + {54, 50, 1, 7, 5, 5}, + + {55, 56, 36, 9, 4, 0}, + {56, 57, 27, 9, 4, 1}, + {57, 58, 18, 9, 4, 2}, + {58, 59, 9, 9, 4, 3}, + {59, 56, 0, 9, 4, 4}, + + {60, 61, 36, 18, 2, 0}, + {61, 62, 18, 18, 2, 1}, + {62, 61, 0, 18, 2, 2} +}; +#endif /* KLH10_EXTADR */ + +#ifndef KLH10_USE_CANBP +# define KLH10_USE_CANBP KLH10_EXTADR +#endif +#if KLH10_EXTADR && !KLH10_USE_CANBP +# error "Cannot turn off KLH10_USE_CANBP with extended addressing!" +#endif + +#if KLH10_USE_CANBP + +/* Struct used to canonicalize byte pointers */ +struct canbp { + int p; /* P - current position (bits from low end) */ + int s; /* S - size (# bits in byte) */ + vaddr_t y; /* Y - current word address */ + w10_t bmsk; /* Byte mask of S bits */ +#if 0 + w10_t w; /* Original BP (1st word) */ + int fmt; /* BP format */ +# define BPF_L1 0 /* 1-word local */ +# define BPF_G1 1 /* 1-word global */ +# define BPF_2 2 /* 2-word local/global */ + /* otherwise high 6 P&S bits */ +#endif +}; + + +/* CBPGET - Set up canonical BP structure. +** BP - pointer to BP struct to use. +** E - EA of byte pointer word. +** INC - 3-way action branch flag: +** >0 - Increment, set FPD, and compute EA that BP points to, +** =0 - No increment, just set up BP including EA computation. +** <0 - Increment only, no FPD setting or EA computation. +** +** Returns TRUE if struct set up, otherwise caller should invoke i_muuo(). +** +** Is there any way to make more of this routine be inline?? +*/ +static int +cbpget(register struct canbp *bp, register vaddr_t e, int inc) +{ + register w10_t w; + register vmptr_t vp; + register int p, s; +#if KLH10_EXTADR + register vmptr_t vp2; +#endif + + /* Find loc of BP using appropriate access */ + vp = (inc ? vm_modmap(e) : vm_xrwmap(e, VMF_READ)); /* May pagefail */ + w = vm_pget(vp); /* Get the byte pointer */ + + p = LHGET(w) >> 12; /* P in high 6 bits */ + +#if KLH10_EXTADR /* Permit OWGBPs in any section */ + if (p > 36) { + register struct owgbpe *tp; + if (p >= 63) + return FALSE; /* return i_muuo(op, ac, e); */ + tp = &owgbptab[p - 37]; + if (inc) { /* Incrementing BP before use? */ + if (tp->pands > tp->pnext) /* If OWGBP going to next word, */ + op10m_inc(w); /* bump the address */ + tp = &owgbptab[tp->pnext - 37]; /* Find next P&S stuff */ + op10m_tlz(w, 0770000); /* Clear high 6 bits for new P&S */ + op10m_tlo(w, (h10_t)(tp->pands) << 12); + vm_pset(vp, w); /* Store new BP back */ + if (inc > 0) { + PCFSET(PCF_FPD); /* Say first-part-done */ + va_gfrword(bp->y, w); /* Get full address */ + } + } else + va_gfrword(bp->y, w); /* Get full address */ + + + /* Later could perhaps make owgbpe same as canbp and return ptr? */ + bp->p = tp->p; + bp->s = tp->s; + return TRUE; + } + + /* Not a OWGBP, see if it's a TWGBP. + ** See if OK to check for one-word or not, by testing section part of + ** the EA we used to fetch the BP. This test is valid even under + ** PXCT! + */ + s = (LHGET(w) >> 6) & 077; /* S in next 6 */ + if (va_isext(e) /* See if section non-zero */ + && op10m_tlnn(w, BPF_2WD)) { /* Yep, see if BP is 2-word kludge. */ + vaddr_t e0 = e; /* Save E before increment */ + + /* Handle two-word BP, yuck ptooey */ + va_inc(e); /* Bump up to E+1 to get next word */ + if (inc) { + register h10_t hi6; + + vp2 = vm_modmap(e); /* Get E+1, may pagefail */ + w = vm_pget(vp2); + if ((p -= s) < 0) { /* Find new P */ + p = (W10BITS - s) & 077; /* Moving to next word */ + /* Tricky part -- bump Y of 2nd word appropriately */ + if (op10m_skipl(w)) { /* See if IFIW local word */ + RHSET(w, (RHGET(w)+1)&H10MASK); /* Yup */ + } else { /* Ugh, EFIW global */ + hi6 = LHGET(w) & 0770000; + op10m_inc(w); /* Add 1 to word */ + op10m_tlz(w, 0770000); + op10m_tlo(w, hi6); /* Put back in word */ + } + vm_pset(vp2, w); /* Now store back 2nd word */ + } + /* Now store back new P */ + hi6 = (vm_pgetlh(vp) & ~0770000) | ((h10_t)p << 12); + vm_psetlh(vp, hi6); + if (inc > 0) + PCFSET(PCF_FPD); + } else /* inc == 0, No BP increment, just fetch 2nd word */ + w = vm_read(e); + + /* Interpret word as either an IFIW or EFIW. + ** Note this EA-calc is subject to hairy PXCT stuff! + ** See comments for OWLBP case for more detail. + */ + if (inc >= 0) { + if (op10m_skipl(w)) { /* IFIW? */ + if (op10m_tlnn(w, IW_EI)) { /* Bits 0,1 == 11? */ + /* Generate page fail trap for bits 0,1 == 11 */ + /* This is failure code 24 (PRM 3-41) */ + pag_iifail(e, cpu.vmap.xrw); /* Unsure what args to give */ + } + /* Local-format, need to decide what default sect is */ + if (cpu.mr_inpxct & 02) + va_xeabpcalc(bp->y, w, pag_pcsget()); /* Use PCS */ + else + va_xeabpcalc(bp->y, w, va_sect(e0)); /* Use E0's */ + } else /* EFIW, no default section needed */ + va_xeaefcalc(bp->y, w, cpu.acblk.xbea, cpu.vmap.xbea); + + if (cpu.mr_inpxct /* If in PXCT */ + && (cpu.mr_inpxct & 01) /* and byte data is prev ctxt */ + && !(cpu.mr_inpxct & 02) /* and byte E isn't */ + && va_islocal(bp->y)) /* and E is local */ + va_lmake(bp->y, /* then must use PCS for section #! */ + pag_pcsget(), va_insect(bp->y)); + } + bp->p = p; + bp->s = s; + return TRUE; + } +#else /* end EXTADR */ + s = (LHGET(w) >> 6) & 077; /* S in next 6 */ +#endif /* !EXTADR */ + + /* Handle one-word local BP */ + if (inc) { + if ((p -= s) < 0) { /* Find new P */ + p = (W10BITS - s) & 077; /* Moving to next word */ + RHSET(w, (RHGET(w)+1)&H10MASK); /* Assume local-format */ + } + /* Now store back new P */ + op10m_tlz(w, 0770000); + op10m_tlo(w, ((h10_t)(p) << 12)); + vm_pset(vp, w); /* Store updated BP */ + if (inc > 0) + PCFSET(PCF_FPD); + } + + /* Find effective address of BP. + ** The initial default section is always that from which the 1st BP + ** word was fetched. + ** *HOWEVER*, if a PXCT is in progress with bit 11 set (E2), this is + ** replaced with the PCS (Prev Ctxt Sect) before proceeding with the + ** EA calculation. + ** A PXCT with bit 12 set (D2) can result in post-processing + ** of the resulting EA, but this only happens if bit 11 wasn't set. + */ + if (inc >= 0) { +#if KLH10_EXTADR + if (cpu.mr_inpxct) { /* If in PXCT, ugly hackery. */ + if (cpu.mr_inpxct & 02) /* Computing EA in prev ctxt? */ + va_xeabpcalc(bp->y, w, pag_pcsget()); /* Yes, use PCS+XBEA */ + else { + va_xeabpcalc(bp->y, w, va_sect(e)); /* No, normal (XBEA) */ + if (va_islocal(bp->y) /* If result is local */ + && (cpu.mr_inpxct & 01)) /* and byte E is prev ctxt */ + va_lmake(bp->y, /* then must use PCS! */ + pag_pcsget(), va_insect(bp->y)); + } + } else + va_xeabpcalc(bp->y, w, va_sect(e)); /* Set Y, use XBEA mapping */ +#else + bp->y = ea_bpcalc(w); +#endif + } + bp->p = p; + bp->s = s; + return TRUE; +} +#endif /* KLH10_USE_CANBP */ + +insdef(i_adjbp); /* Forward decl for adjbp */ + +insdef(i_ibp) +{ + if (ac) + return i_adjbp(op, ac, e); + { +#if KLH10_USE_CANBP + struct canbp cbp; + + if (!cbpget(&cbp, e, -1)) /* Set up BP info, increment only */ + return i_muuo(op, ac, e); +#else + + register vmptr_t vp = vm_modmap(e); /* Get pointer to BP, normal mapping */ + register w10_t bp = vm_pget(vp); /* Fetch it */ + register int p = LHGET(bp) >> 12; /* P in high 6 bits */ + register int s = (LHGET(bp) >> 6) & 077; /* S in next 6 */ + +#define IBPMACRO(vp,bp,p,s) \ + { \ + if ((p -= s) < 0) { /* Decrement P by S bits */\ + RHSET(bp, (RHGET(bp)+1)&H10MASK); /* Add 1 to Y */\ + p = (36 - s) & 077; \ + } \ + LHSET(bp, (LHGET(bp) & 07777) | ((uint18)p << 12)); /* Put P back in */\ + vm_pset(vp, bp); /* Store the BP back in memory */\ + } + IBPMACRO(vp, bp, p, s) /* Increment BP and store back */ + +#endif /* !KLH10_USE_CANBP */ + } + return PCINC_1; +} + +insdef(i_ldb) +{ +# define LDBMACRO(p, s, y) \ + { \ + register w10_t w; \ + w = vm_pget(vm_xbrwmap(y, VMF_READ)); /* Fetch word (byte map) */\ + op10m_rshift(w, p); /* Shift word to right-align byte */\ + op10m_and(w, wbytemask[s]); /* Mask out byte */\ + ac_set(ac, w); /* Store byte in AC */\ + } + +#if KLH10_USE_CANBP + struct canbp cbp; + + if (!cbpget(&cbp, e, 0)) /* Set up BP info, no increment */ + return i_muuo(op, ac, e); + + LDBMACRO(cbp.p, cbp.s, cbp.y) /* Get byte into AC! */ + +#else + register w10_t bp = vm_read(e); /* Get BP, (normal map) */ + register vaddr_t y = ea_bpcalc(bp); /* Find its E, (special map) */ + register int p = LHGET(bp) >> 12; /* Get P field */ + register int s = (LHGET(bp) >> 6) & 077; /* And S field */ + + LDBMACRO(p, s, y) /* Get byte into AC! */ +#endif + + return PCINC_1; +} + +insdef(i_ildb) +{ +#if KLH10_USE_CANBP + struct canbp cbp; + + /* Set up BP info, increment pointer unless FPD flag is set */ + if (!cbpget(&cbp, e, PCFTEST(PCF_FPD) ? 0 : 1)) + return i_muuo(op, ac, e); + LDBMACRO(cbp.p, cbp.s, cbp.y) /* Invoke shared LDB code */ + +#else + + register vmptr_t vp = vm_modmap(e); /* Get pointer to BP, normal mapping */ + register w10_t bp = vm_pget(vp); /* Fetch it */ + register int p = LHGET(bp) >> 12; /* P in high 6 bits */ + register int s = (LHGET(bp) >> 6) & 077; /* S in next 6 */ + + if (!PCFTEST(PCF_FPD)) { /* Unless already did it, */ + IBPMACRO(vp, bp, p, s) /* Increment pointer */ + } + PCFSET(PCF_FPD); /* Say "First Part Done" in case pagefault */ + { + register vaddr_t y = ea_bpcalc(bp); /* Find BP's E (special map) */ + + LDBMACRO(p, s, y) /* Now do the LDB into AC */ + } +#endif + + PCFCLEAR(PCF_FPD); /* Won, clear flag */ + return PCINC_1; +} + + +insdef(i_dpb) +{ +#define DPBMACRO(p, s, y) \ + { \ + register w10_t w; \ + register w10_t bmask, byte; \ + register vmptr_t bvp; \ + \ + bvp = vm_xbrwmap(y, VMF_WRITE); /* Use special byte data map */\ + byte = ac_get(ac); /* Fetch source byte */\ + bmask = wbytemask[s]; /* Get & shift byte-sized mask */\ + op10m_lshift(bmask, p); \ + op10m_lshift(byte, p); /* Left-shift to position in word */\ + op10m_and(byte, bmask); /* Mask off the source byte */\ + w = vm_pget(bvp); /* Fetch dest word */\ + op10m_andcm(w, bmask); /* And clear dest byte in word */\ + op10m_ior(w, byte); /* Now can IOR byte into word */\ + vm_pset(bvp, w); /* Store back in memory */\ + } + +#if KLH10_USE_CANBP + struct canbp cbp; + + if (!cbpget(&cbp, e, 0)) /* Set up BP info, no increment */ + return i_muuo(op, ac, e); + + DPBMACRO(cbp.p, cbp.s, cbp.y) /* Do the DPB from AC */ + +#else + + register w10_t bp = vm_read(e); /* Get byte pointer, normal map */ + register vaddr_t y = ea_bpcalc(bp); /* Calculate E that BP points to */ + register int p = LHGET(bp) >> 12; /* Get P */ + register int s = (LHGET(bp) >> 6) & 077; /* Get S */ + + DPBMACRO(p, s, y) /* Do the DPB from AC */ + +#endif + return PCINC_1; +} + +insdef(i_idpb) +{ +#if KLH10_USE_CANBP + struct canbp cbp; + + /* Set up BP info, increment pointer unless FPD flag is set */ + if (!cbpget(&cbp, e, PCFTEST(PCF_FPD) ? 0 : 1)) + return i_muuo(op, ac, e); + DPBMACRO(cbp.p, cbp.s, cbp.y) /* Invoke code shared with DPB */ +#else + + register vmptr_t vp = vm_modmap(e); /* Get pointer to BP, normal mapping */ + register w10_t bp = vm_pget(vp); /* Fetch it */ + register int p = LHGET(bp) >> 12; /* P in high 6 bits */ + register int s = (LHGET(bp) >> 6) & 077; /* S in next 6 */ + + if (!PCFTEST(PCF_FPD)) { /* Unless already did it, */ + IBPMACRO(vp, bp, p, s) /* Increment pointer */ + } + PCFSET(PCF_FPD); /* Say "First Part Done" in case pagefault */ + { + register vaddr_t y = ea_bpcalc(bp); /* Find BP's E (special map) */ + + DPBMACRO(p, s, y) /* Do the DPB from AC */ + } +#endif + PCFCLEAR(PCF_FPD); /* Won, clear flag */ + return PCINC_1; +} + +/* IBP/ADJBP emulation. +** Following description taken from DEC hardware manual. Integer +** divisions, of course. +** Let A = rem((36-P)/S) +** If S > 36-A set no divide & exit +** If S = 0 set (E) -> (AC) +** If 0 < S <= 36-A: NOTE: Dumb DEC doc claims < instead of <= !!! +** L = (36-P)/S = # bytes to left of P +** B = L + P/S = # bytes to left + # bytes to right = # bytes/word +** Find Q and R, Q*B + R = (AC) + L +** where 1 <= R <= B ; that is, not neg or zero! +** Then: +** Y + Q -> new Y ; must wraparound correctly. +** 36 - R*S - A -> new P +** Put new BP in AC. Only P and Y fields changed, not S, I, X. +*/ +/* NOTE: +** There is a fair amount of code here to handle the special case +** where the count has a magnitude greater than 31 bits (which is the +** most we are portably guaranteed to have in native mode). +** It will virtually never be executed, but is necessary. It uses +** a few tricks to avoid making full-blown exceedingly slow calls to +** the idiv emulation. +** Later a check should be made for the existence of a +36 bit type and +** that native type used. +*/ + +/* ADJBP is a pseudo-instruction variant of IBP, called if AC != 0 */ + +insdef(i_adjbp) +{ + register w10_t bp; + register w10_t cw; /* Count word */ + register int32 y, q; + register int p, s, b, r; /* 16-bit prec OK for these vars */ + int sign; + + bp = vm_read(e); /* Fetch BP, normal mapping */ + p = LHGET(bp) >> 12; /* P in high 6 bits */ +#if KLH10_EXTADR + if (p > 36) { + register struct owgbpe *tp; /* Handle OWGBP */ + + if (p >= 63) + return i_muuo(op, ac, e); + tp = &owgbptab[p - 37]; + b = tp->bpw; /* Find # bytes per word */ + + cw = ac_get(ac); /* Get +/- # bytes from c(AC) */ + op10m_addi(cw, tp->btl); /* Add positive # bytes on left */ + if (op10m_skipl(cw)) { + sign = -1; + op10m_movn(cw); + } else sign = 0; + if (op10m_tlnn(cw, 0760000)) { /* If any high 5 bits are set */ + + /* Ugh, do full-blown word operations */ + switch (b) { + case 6: /* Ugh. Size 6 is 6 bytes per word. */ + case 5: /* Ugh. Size 7 is 5 bytes per word. */ + /* Break up division into halfwords */ + y = LHGET(cw) / b; + r = LHGET(cw) % b; + q = (((int32)r << 18) | RHGET(cw)) / b; + r = (((int32)r << 18) | RHGET(cw)) % b; + if (q & ~H10MASK) /* Just call me paranoid */ + panic("i_adjbp: too-high quotient bits?! (%lo)\r\n", + (long)q); + + LRHSET(cw, y, q); + break; + + case 4: /* Sizes 8 and 9 are easy; 4 bytes per word */ + r = RHGET(cw) & 03; /* Save remainder */ + op10m_rshift(cw, 2); /* Divide by 4 */ + break; + case 2: /* Size 18 is easy too - also power of 2 */ + r = RHGET(cw) & 01; /* Save remainder */ + op10m_rshift(cw, 1); /* Divide by 2 */ + break; + } + if (sign) + op10m_movn(cw); /* Negate Q for proper sign */ + if (sign || !r) { /* If R <= 0 (always true if count neg) */ + r = b - r; /* then adjust to 0 < R, which means --Q too */ + op10m_dec(cw); + } + } else { + + /* Whew, can use native mode! */ + y = ((int32)LHGET(cw) << 18) | RHGET(cw); + q = y / b; /* Find # words to adjust */ + r = y % b; /* And # bytes left over */ + if (sign) + q = -q; /* Negate Q for proper sign */ + if (sign || !r) { /* If R <= 0 (always true if count neg) */ + r = b - r; /* then adjust to 0 < R, which means --Q too */ + --q; + } + LRHSET(cw, (q>>18)&H10MASK, (q & H10MASK)); + } + op10m_add(bp, cw); /* Add Q into BP global addr */ + op10m_tlz(bp, 0770000); /* Clear bits for new P&S */ + + /* Note cleverness here that derives offset from current TP by + ** subtracting "btl" to get to top of grouping, then adding "r" to + ** find correct P&S entry for the remainder. + */ + op10m_tlo(bp, /* Insert new P&S value */ + ((h10_t)tp[r - tp->btl].pands) << 12); + ac_set(ac, bp); /* Return new OWGBP in AC */ + return PCINC_1; + } +#endif /* KLH10_EXTADR */ + + /* Not a OWGBP, see if 2-word global or 1-word local */ + s = (LHGET(bp) >> 6) & 077; /* Get and test S */ + if (s == 0) { /* If S == 0 just set c(E) -> c(AC) */ +#if KLH10_EXTADR + /* Special check to see if E+1 must be copied too */ + if (va_isext(e) /* See if section non-zero */ + && op10m_tlnn(bp, BPF_2WD)) { /* Yep, see if BP is two-word kludge */ + va_inc(e); /* Bump up to E+1 to get next word */ + ac_set(ac_off(ac,1), vm_read(e)); /* Get it, may page-fail */ + } +#endif + } else { + register int l, a; /* 16-bit prec OK for these vars */ + + /* Now derive A = rem((36-P)/S) and test it */ + l = (36 - p) / s; /* Find # bytes to left of P */ + a = (36 - p) % s; /* Find rem (unbyted bits to left of P) */ + if (s > (36-a)) { /* Check alignment */ + PCFTRAPSET(PCF_TR1|PCF_ARO|PCF_DIV); /* Ugh, barf. */ + return PCINC_1; /* Don't modify AC */ + } + b = l + (p/s); /* # bytes to left + # bytes to right */ + /* gives us # bytes per word */ + + cw = ac_get(ac); /* Get +/- # bytes from c(AC) */ + op10m_addi(cw, l); /* Add positive # bytes on left */ + if (op10m_skipl(cw)) { + sign = -1; + op10m_movn(cw); + } else sign = 0; + if (op10m_tlnn(cw, 0760000)) { /* If any high 5 bits are set */ + + /* Ugh, do full-blown word operations */ + /* Break up division into halfwords */ + y = LHGET(cw) / b; + r = LHGET(cw) % b; + q = (((int32)r << 18) | RHGET(cw)) / b; + r = (((int32)r << 18) | RHGET(cw)) % b; + if (q & ~H10MASK) /* Just call me paranoid */ + panic("i_adjbp: too-high quotient bits?! (%lo)\r\n", + (long)q); +#if KLH10_EXTADR /* Only keep LH if might need for 2-wd BPs */ + LRHSET(cw, y, q); + if (sign) + op10m_movn(cw); /* Negate Q for proper sign */ + if (sign || !r) { /* If R <= 0 (always true if count neg) */ + r = b - r; /* then adjust to 0 < R, which means --Q too */ + op10m_dec(cw); + } + q = RHGET(cw); +#else + q = (y << 18) | q; /* Make +30-bit native integer */ + if (sign) + q = -q; /* Negate Q for proper sign */ + if (sign || !r) { /* If R <= 0 (always true if count neg) */ + r = b - r; /* then adjust to 0 < R, which means --Q too */ + --q; + } +#endif + } else { + /* Whew, can use native mode! */ + y = ((int32)LHGET(cw) << 18) | RHGET(cw); + q = y / b; /* Find # words to adjust */ + r = y % b; /* And # bytes left over */ + if (sign) + q = -q; /* Negate Q for proper sign */ + if (sign || !r) { /* If R <= 0 (always true if count neg) */ + r = b - r; /* then adjust to 0 < R, which means --Q too */ + --q; + } +#if KLH10_EXTADR /* Only keep LH if might need for 2-wd BPs */ + LRHSET(cw, (q>>18)&H10MASK, (q & H10MASK)); +#endif + } + + /* Q now contains at least 18 bits of the word count adjustment. + ** CW contains the entire 36 bits, if necessary. + */ + p = (36 - r*s) - a; /* Have R, can now find new P */ + +#if KLH10_EXTADR + if (va_isext(e) /* See if section non-zero */ + && op10m_tlnn(bp, BPF_2WD)) { /* Yep, see if BP is two-word kludge */ + register w10_t bp2; + + /* Handle two-word BP, yuck ptooey */ + va_inc(e); /* Bump up to E+1 to get next word */ + bp2 = vm_read(e); /* Get it, may page-fail (sigh!) */ + + /* Tricky part -- bump Y of 2nd word appropriately */ + if (op10m_skipl(bp2)) { /* See if IFIW local word */ + RHSET(bp2, (RHGET(bp2) + q)&H10MASK); /* Yup */ + } else { /* Ugh, EFIW global word */ + op10m_add(cw, bp2); /* Add words together (RH can carry) */ + LRHSET(bp2, /* Save high 6, use low 30 bits */ + (LHGET(bp2) & 0770000) | (LHGET(cw) & 07777), + RHGET(cw)); + } + ac_set(ac_off(ac,1), bp2); + } else +#endif + RHSET(bp, (RHGET(bp) + q)&H10MASK); /* New Y = Y + Q */ + LHSET(bp, (LHGET(bp) & 07777) | ((h10_t)p << 12)); /* New P */ + } + + /* Now store 1st (maybe only) word of BP to wrap up */ + ac_set(ac, bp); + return PCINC_1; +} diff --git a/src/inexts.c b/src/inexts.c new file mode 100644 index 0000000..9e5cca8 --- /dev/null +++ b/src/inexts.c @@ -0,0 +1,2550 @@ +/* INEXTS.C - Extended (String) Instruction routines +*/ +/* $Id: inexts.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: inexts.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* See CODING.TXT for guidelines to coding instruction routines. */ + +#include +#include "klh10.h" +#include "kn10def.h" /* Machine defs */ +#include "kn10ops.h" /* PDP-10 ops */ + +#ifdef RCSID + RCSID(inexts_c,"$Id: inexts.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Exported functions (other than instrs) */ + +void inexts_init(void); /* Initialize any necessary EXTEND stuff */ + +/* Local predeclarations */ + +#if KLH10_EXTADR +static int xea_fiwcalc(w10_t iw, unsigned int sect, + acptr_t acp, pment_t *map, vaddr_t *va, int f); +#endif + +/* Notes: +** +** + The EXTEND string instructions are documented to be undefined when +** used with PXCT (except for MOVSLJ), but appropriate mapping is +** done anyway since it costs nothing and lets MOVSLJ work. This may +** change with extended addressing. +** + The G-Format extended instructions are handled in inflt.c. +*/ + +/* NOTES: + The EXTEND instructions, as described by the PRM, contain a lot +of ambiguities as to exactly how they behave in response to unusual +conditions, especially with respect to the resulting AC contents. Here +is a list of unclear points, with the assumptions made by KLH10 code. + +* Are the AC bits at c(E0) checked? PRM only says (p.1-25) "must be zero". + [ANSWER: KS: AC bits MUST == 0 or it traps as a MUUO. + KL: AC bits ignored. + ] +* Is E1 always computed whether or not it is used? + [Assume YES, based on PRM 1-25 top paragraph: + "As with all instructons, before executing the second word the + processor calculates an effective address for it; this is referred + to as E1, ..." + ANSWER: YES, if: + KS: opcode is 0-17 incl and AC=0 (else MUUO taken before E1 calc) + KL: opcode is 0-31 incl (else MUUO taken) + and not 20 (else E1 skipped) + ] +* Are fill bytes from c(E0+1), c(E0+2) always pre-fetched (liable to page + fault before anything done), or only referenced when and if needed? + [ANSWER: + KS/KL: pre-fetched for all MOVSx instructions. + KS/KL: pre-fetched for all CMPSx, but KS/KL differ if lengths equal + (see comments at ix_cmps). + KS/KL: CVTBDx only fetches fill if needed. + KS: pre-fetched for EDIT (but float char only fetched as needed). + KL: both fill and float only fetched as needed. + ] +* What happens if the zero-specified high bits in string counts are + non-zero? + [ANSWER: traps as MUUO] + +* Are the high 9 bits of string lengths used as flags for intermediate states, + such as if a page fault interrupts between IBP and LDB? + [ANSWER: No -- kept clear.] + +* Are OWG byte pointers left alone if a page fault happens on the first ref, + or have they already been converted (so that initial P is lost)? + [ANSWER: already converted] + +* Are byte pointers converted so that I and X are zero? If not, are the + X and I references made with every use of the BP? What happens if + I is set? + [ANSWER: NO! Apparently EA is recomputed on each BP ref. + I and X are retained just as for normal ILDB/IDPB. + Implementation here simulates this without actually doing the + recomputation unless indirection makes it necessary. + Of course, all that work is probably pointless because + any user program that sets I won't work in general, + because a new word is indirected through as soon as Y + is incremented!] + +* What happens if a 2-word BP has its 2nd word in IFIW format instead + of EFIW -- is it converted to EFIW or left as IFIW? If IFIW, are resulting + pointer EAs re-computed with every reference to check for insection overflow? + Argh! + [ANSWER: no conversion is done; EFIW/IFIW distinction is maintained + and addresses are incremented appropriately for the format, i.e. + IFIW insection overflow just wraps. + Furthermore, having both bit 0,1 set in 2nd word causes an illegal + indirection page fault.] + +* NOTE! From empirical testing, an II page fault prior to any string use of BP + results in BP having been incremented, but P is backed up so that + a re-invocation will still refer to the same byte. + e.g. initial BP: 004440,,0 ? 600001,,70 + result BP: 444440,,0 ? 600001,,71 + +* Since OWGBPs are allowed in section 0 by the byte instructions, do the + EXTEND string instructions convert them? But if instr is restarted, TWGBPs + will fail because they can't be used in section 0! What's the plan?? + [ANSWER: OWGBPs not allowed in sect 0 by EXTEND instrs; they're + interpreted as OWLBPs.] + +* When a OWGBP is converted to a TWGBP, is the 2nd word an EFIW or IFIW? + [ANSWER: EFIW with I+X= 0] + +* What happens if BPs are not aligned? (see also MOVSRJ question) + [ANSWER: behave just as if using ILDB/IDPBs -- they stay unaligned + until a word boundary is crossed, whereupon they are aligned. + This applies to a MOVSRJ skip as well.] + +* Do translation tables cross section boundaries? + [Assume E1+offset follows same rules as normal E+offset, i.e. + depends on whether E1 is local or global. + WRONG! On real KL, always crosses section boundaries, EVEN IF + RUNNING IN ZERO SECTION, regardless of locality of E1.] + +* Is there any limit on the size of a translation table? + [ANSWER: No -- simply adds byte/2 to E1. Can jump entire + sections!] + +* Does extended address computation for E1 depend on location of word at E0, + or on PC? What about computation of addresses for translation table entries? + [Assume depends on location of word addressed by E0. + Also assume E1+offset follows same rules as normal E+offset.] + +-------------------- +* MOVSO: does the offset apply to the fill byte? Is the fill byte tested? + [ANSWER: No to both.] +* MOVSO: is the oversize byte test applied with source or dest byte size? + [ANSWER: Dest byte size] +* MOVSO: what is state of ACs if instr stops due to oversize byte? Is source + BP & count pointing to guilty byte or backed up? + [Assume backed up, so a re-try will fail identically] + +-------------------- +* MOVST: If a translation function terminates, is the rest of the dest string + filled or not? + [ANSWER: No - not filled.] + +-------------------- +* MOVSRJ: Does this really always skip, even if some source bytes are left? + [ANSWER: Yes - by definition no source bytes are ever left!] +* MOVSRJ: If source bytes are skipped, are any memory refs made (ie possible + to page-fail) or is the source pointer merely bumped? + [ANSWER: No mem refs - src pointer is simply bumped. !!! BUT !!! + On the KL, the src skip is always made as if the 1st wd was a + OWLBP, regardless of actual format, thus this skip loses if BP + is really a OWG, TWG, or TWL!!! Ucode bug!] +* MOVSRJ: If source bytes skipped, is the BP bumped as if ILDB were done, or + as if ADJBP (which preserves possible non-alignment)? + [ANSWER: Bumped as if IBP, thus non-alignment is only preserved until + the first word increment.] + +-------------------- +* CMPSx: Are the bytes signed for comparison purposes? + [Assuming unsigned] +* CMPSx: What about full-word 36-bit bytes? (CAM uses signed compare) + [Assuming unsigned for consistency] +* CMPSx: What happens to the length and pointer of a string that counts out + and has its fill byte used? + [Assuming length remains 0 and pointer is left pointing to last byte] +* CMPSx: Are the high bits of the fill-byte words used, or are they masked + out before the comparison? + [NO! Formerly assumed masked out, but empirical testing shows this + is not the case. Fill word is used as a whole.] + +-------------------- +* CVTBDx: Must the N & M bits be clear initially? Is their value ignored? + [Assuming value ignored, since may be restarting] +* CVTBDx: Are the N and M bits always set to either 0 or 1, or are new + settings just IOR'd in? + [Assuming IOR'd, since may be restarting instruction] +* CVTBDx: Is the low-order sign bit ignored in the setting of N? + [Assuming yes] +* CVTBDx: What happens for max negative integer (which cannot be readily + negated into positive form)? + [Assuming code does right thing & generates correct value] +* CVTBDx: Does a page-fault or interrupt really update all ACs by + storing back new binary # and new pointer/length, or does it + always either run to completion or restore original ACs? + [Assuming former, updates all ACs with partial results] +* CVTBDx: Does CVTBDO check result byte for being oversize, like MOVSO? + [Assuming not] + +-------------------- +* CVTBDT: What happens if translated digit has a 4-bit value greater + than 9? Is check made after translation or are the bits just masked? + [Assume bits just masked, don't test for 9 < x <= 017] +* CVTBDT: What are source len and BP values if string is terminated, or + aborted because of a bad digit? + [Assume points to terminating or bad byte, with len indicating + remainder of string] + +-------------------- +* CVTDBT: What happens if last source byte forces termination? Does it + skip because instruction ate all bytes, or not skip because a + termination happened? + [Assume no skip] + +-------------------- +* EDIT: What happens if an illegal command byte is seen? + [Assume updates ACs, so a re-xct will immediately fail + on that byte as well, and then MUUO-fails] + [WRONG! Diagnostic simulation simply treats unrecognized + pattern bytes as NOPs. Ucode agrees. Sigh.] +*/ + + +/* See opdefs.h for the definition of xinsdef(), +** which is used to define all extended instruction routines. +*/ + +insdef(i_extend) +{ + register w10_t xw; + register unsigned int xop; + + xw = vm_read(e); /* Get contents of E0 using normal XRW map */ + xop = iw_op(xw); /* Find extended opcode */ + + /* Check extended opcode (and perhaps AC) before doing indexed dispatch. + Note that KL doesn't check AC field, whereas KS requires that + it be zero! + */ +#if KLH10_CPU_KLX + if (xop >= IX_N) + return i_muuo(op, ac, e); /* Bad op */ + +#elif KLH10_CPU_KS +# if (IX_N == 040 || IX_N == 020) /* Faster check if power of 2 */ + if (LHGET(xw) & (((~(IX_N-1))<<9) | (AC_MASK<<5))) /* Check op and AC fields */ +# else + if (xop >= IX_N || (LHGET(xw) & (AC_MASK<<5))) /* Check op, AC field */ +# endif + return i_muuo(op, ac, e); /* Bad op or AC */ +#endif + + /* Calculate E1 using normal XEA map, which may page-fail! + ** Following the normal instruction model, + ** E1 would be calculated before looking at anything else (OP or AC). + ** However... + ** For both KS and KL this is always done after the initial OP+AC check, + ** regardless of whether the instruction needs E1 or not, with + ** one exception: on the KL, XBLT skips the E1 calc entirely! + ** (this exception doesn't seem worth emulating) + */ +#if KLH10_EXTADR + /* Default section for 2nd instr word EA calc is the section + ** that word was fetched from. + */ + return (*opcxrtn[xop])(xop, ac, e, xea_calc(xw, va_sect(e))); +#else + return (*opcxrtn[xop])(xop, ac, e, ea_calc(xw)); +#endif +} + +xinsdef(ix_undef) +{ + return i_muuo(I_EXTEND, ac, e0); +} + + +#if KLH10_SYS_T10 || KLH10_SYS_T20 /* DEC systems only */ + +/* All of the remaining code in this module is ignored for an ITS system, +** since the extended instructions were all tossed from the ITS ucode. +*/ + +/* AC flags for Binary<->Decimal conversions */ + +#define CVTF_L H10SIGN /* User sets to control justification */ +#define CVTF_S H10SIGN /* Another name for same bit */ +#define CVTF_N (H10SIGN>>1) /* Instr sets 1 if non-zero */ +#define CVTF_M (H10SIGN>>2) /* Instr sets 1 if negative */ +#define CVTF_ALL (CVTF_L|CVTF_N|CVTF_M) + +/* Internal result codes for the various instructions */ +/* NOTE: See EA_RES_PF, EA_RES_PI defs; must match RES_PF, RES_PI! */ +enum xires { + RES_OK=0, /* 0 is general non-error status */ + RES_TRUNC=1, /* Set 1 for efficient code (usu. returns PCINC_1) */ + RES_WON=2, /* Ditto (usu. returns PCINC_2) */ + RES_PF, /* Page Fail */ + RES_PI, /* PI Interrupt */ + RES_MUUO }; /* MUUO trap */ + + +/* Auxiliary power-of-10 table for CVT instructions */ +#define DPOW_MAX 23 +static dw10_t dpow10[DPOW_MAX]; /* Later maybe define at compile time */ + +void +inexts_init(void) /* Initialize any necessary EXTEND stuff */ +{ + register int i; + register dw10_t d; + h10_t savflgs; + + op10m_setz(d.w[0]); /* Set double fix to 1 */ + XWDSET(d.w[1], 0, 1); + dpow10[0] = d; /* Store 1 as first table entry */ + XWDSET(d.w[1], 0, 10); /* Then set it to 10 */ + + /* Compute rest of power-of-10 table, ignoring any overflow traps */ + savflgs = cpu.mr_pcflags; + for (i = 1; i < DPOW_MAX; ++i) { + qw10_t q; + q = op10dmul(dpow10[i-1], d); + dpow10[i] = q.d[1]; + } + cpu.mr_pcflags = savflgs; +} + + +/* Other auxiliaries */ + +/* Macro to fetch string length into a native 32-bit type for efficiency. +** Does a mask test to see whether length word has any illegal bits set +** and traps as a MUUO if so. +*/ +#define AC_32GET(r, a, off, lmask) \ + { register w10_t w; \ + w = ac_get(ac_off(a, off)); \ + if (op10m_tlnn(w, lmask)) \ + return i_muuo(I_EXTEND, (a), e0); \ + r = ((uint32)LHGET(w) << H10BITS) | RHGET(w); /* Convert to 32-bit value */ \ + } + +#if KLH10_EXTADR +# define ILLEG_LHVABITS 0770000 /* Bits illegal in LH of 30-bit virt addr */ +#else +# define ILLEG_LHVABITS 0777777 +#endif + +#if 0 +static uint32 +ac_32get(ac) +{ + register w10_t w; + w = ac_get(ac); /* Get c(AC) */ + return ((uint32)LHGET(w) << 18) | RHGET(w); /* Convert to 32-bit value */ +} +#endif /* 0 */ + +static void +ac_32set(int ac, uint32 val) +{ + register w10_t w; + LRHSET(w, (val>>18)&H10MASK, val & H10MASK); /* Put val into word */ + ac_set(ac, w); /* Store in AC */ +} + +#if KLH10_CPU_KLX + +/* XBLT +** NOTE: Per [Uhler83], XBLT is allowed from section 0 on a KLX! +** On the KS10 there is only section 0, so this always traps as a +** MUUO on that machine. Assume single-section KL similar. +** +** This is a straightforward implementation and there are several +** ways to optimize it: +** - Separate loops for forward and reverse BLTs +** - Test for 32-bit count and optimize in register +** - Test for src -> src+1 and avoid re-reading value if mappings same. +** - Test for within-mem-page BLTs and break into memcpy segments. +** +** Note: for PXCT, source uses XBEA mapping, dest uses XBRW. +** These correspond to PXCT AC bits 11 and 12 respectively. +*/ + +xinsdef(ix_xblt) +{ + register acptr_t acp; + register w10_t wc; + register vaddr_t src, dst; + register vmptr_t svp, dvp; + enum xires res = RES_WON; + + acp = ac_map(ac_off(ac,1)); /* Get source pointer and check it */ + if (op10m_tlnn(*acp, ILLEG_LHVABITS)) + return i_muuo(I_EXTEND, ac, e0); + va_gfrword(src, *acp); /* Make global addr from wd */ + + acp = ac_map(ac_off(ac,2)); /* Now get destination */ + if (op10m_tlnn(*acp, ILLEG_LHVABITS)) + return i_muuo(I_EXTEND, ac, e0); + va_gfrword(dst, *acp); /* Likewise get global addr */ + + wc = ac_get(ac); /* Get possibly 36-bit cnt */ + if (op10m_skipge(wc)) { + if (op10m_skipe(wc)) /* If count zero, */ + return PCINC_1; /* return without any refs */ + + /* Do normal transfer */ + for (;;) { + if (!(svp = vm_xbeamap(src, VMF_READ|VMF_NOTRAP))) { + res = RES_PF; + break; + } + if (!(dvp = vm_xbrwmap(dst, VMF_WRITE|VMF_NOTRAP))) { + res = RES_PF; + break; + } + vm_pset(dvp, vm_pget(svp)); /* Transfer the word */ + + va_ginc(src), va_ginc(dst); /* Bump addrs up */ + op10m_dec(wc); /* Bump count down */ + + /* Done with one iteration, now check before doing next */ + if (op10m_skipe(wc)) /* If count gone, */ + break; /* stop loop! */ + + CLOCKPOLL(); /* More left, keep clock going */ + if (INSBRKTEST()) { /* Watch for PI interrupt */ + res = RES_PI; + break; + } + } + + } else { + /* Do reverse transfer */ + for (;;) { + va_gdec(src); /* Bump address down */ + if (!(svp = vm_xbeamap(src, VMF_READ|VMF_NOTRAP))) { + va_ginc(src); /* Foo, restore it */ + res = RES_PF; + break; + } + va_gdec(dst); /* Bump address down */ + if (!(dvp = vm_xbrwmap(dst, VMF_WRITE|VMF_NOTRAP))) { + va_gdec(dst); /* Foo, must restore both */ + va_ginc(src); + res = RES_PF; + break; + } + vm_pset(dvp, vm_pget(svp)); /* Transfer the word */ + + op10m_inc(wc); /* Bump count up */ + + /* Done with one iteration, now check before doing next */ + if (op10m_skipe(wc)) /* If count gone, */ + break; /* stop loop! */ + + CLOCKPOLL(); /* More left, keep clock going */ + if (INSBRKTEST()) { /* Watch for PI interrupt */ + res = RES_PI; + break; + } + } + } + + ac_set(ac, wc); /* Store whatever count is now */ + acp = ac_map(ac_off(ac, 1)); + va_toword(src, *acp); /* Store addr in word */ + acp = ac_map(ac_off(ac, 2)); + va_toword(dst, *acp); + switch (res) { + case RES_PF: pag_fail(); /* Never returns */ + case RES_PI: apr_int(); /* Never returns */ + default: break; + } + return PCINC_1; +} +#endif /* KLH10_CPU_KLX */ + +/* New EA calculation function needed to support string instructions + Later move this to kn10pag.h, kn10cpu.c? +*/ + +enum eaarg { EA_ARG_IFIW=0, EA_ARG_EFIW=1, EA_ARG_UFIW=-1 }; + +/* NOTE!!! + Code assumes EA_RES_PF == RES_PF (ditto RES_PI) for simplicity!!! + */ +enum eares { EA_RES_OK=0, EA_RES_PF=RES_PF, EA_RES_PI=RES_PI }; + +#if KLH10_EXTADR + +/* XEA_FIWCALC - Extended Addressing IFIW/EFIW EA calc +** Returns result code after depositing EA in location provided. +** +** This is exactly the same algorithm as the standard (but faster) +** XEA_XCALC function except that instead of aborting on any error +** (page fail or PI), it returns with an error code. +** Although slower, this functionality is needed to give the extended +** string instructions a chance to clean up. +*/ +static int +xea_fiwcalc(register w10_t iw, /* IFIW/EFIW to evaluate */ + register unsigned int sect, /* Current section, if IFIW */ + register acptr_t acp, /* AC block mapping */ + register pment_t *map, /* Page table mapping */ + register vaddr_t *va, /* Result EA as virtual address */ + int f) /* arg flags */ +{ + register vaddr_t e; /* Address to return */ + + if (f) { + if (f > 0) goto xea_efiw; + else goto xea_ufiw; + } + + /* Handle IFIW - Instruction Format Indirect Word */ + for (;;) { + if (op10m_tlnn(iw, IW_X)) { /* Indexing? */ + register w10_t xw; + xw = ac_xget(iw_x(iw), acp); /* Get c(X) */ + /* Check for type of indexing. + ** Do global only if NZS E, X>0, and X<6:17> NZ + */ + if (op10m_skipge(xw) && sect && op10m_tlnn(xw, VAF_SMSK)) { + /* Note special hackery for global indexing: Y becomes + ** a signed displacement. + */ + va_gmake30(e, VAF_30MSK & (va_30frword(xw) + + (op10m_trnn(iw, H10SIGN) + ? (RHGET(iw) | (VAF_SMSK<src = src; /* Save args */ + bp->ac = ac; + w = ac_get(ac); /* Get the byte pointer */ + bp->p = LHGET(w) >> 12; /* P in high 6 bits */ + +#if KLH10_EXTADR + /* For extended addressing there are 3 cases: + ** OWLBP - One-Word Local BP + ** TWGBP - Two-Word Global BP (only valid in NZ PC sect) + ** OWGBP - One-Word Global BP (only valid in NZ PC sect, immediately + ** converted into a TWGBP) + ** NOTE: this is contrary to the way byte instructions + ** like DPB behave! (They accept OWGBPs in section 0) + */ + if (PC_ISEXT) { + if (bp->p > 36) { /* OWGBP? */ + register struct owgbpe *tp; + if (bp->p >= 63) + return RES_MUUO; /* return i_muuo(op, ac, e); */ + tp = &owgbptab[bp->p - 37]; + bp->p = tp->p; + bp->s = tp->s; + va_gfrword(bp->y, w); /* Get full 30-bit address */ + bp->fmt = BPF_OWG; /* Say originally an OWGBP */ + bp->isindir = FALSE; + + } else if (op10m_tlnn(w, BPF_2WD)) { /* TWGBP? */ + /* It's a TWGBP! + ** Note the NZ-section test above is always made using PC section, + ** unlike byte instrs which always use the section the byte ptr + ** was fetched from. + ** This is actually consistent if you remember that the ACs are + ** considered to belong to PC section. + */ + bp->s = (LHGET(w) >> 6) & 077; /* S in next 6 */ + w = ac_get(ac_off(ac,1)); /* Fetch 2nd word from AC+1 */ + + /* Interpret word as either an IFIW or EFIW. + ** Note this EA-calc is subject to hairy PXCT stuff! + ** Default section is PC section, unless affected by + ** PXCT in which case it's PCS. + ** No postprocessing is needed because both E and D bits are + ** implied if previous context applies to this pointer. + */ + if (op10m_skipl(w)) { /* IFIW? */ + if (op10m_tlnn(w, IW_EI)) { /* Bits 0,1 == 11? */ + /* Generate page fail trap for bits 0,1 == 11 */ + /* This is failure code 24 (PRM 3-41) */ + /* BP came from ACs which are always current context */ + vaddr_t e; /* Set up args for pagefail */ + va_lmake(e, 0, ac_off(ac,1)); + pag_iiset(e, cpu.vmap.cur); + return RES_PF; + } + bp->fmt = BPF_TWL; /* Say originally a TWLBP */ + bp->isindir = (op10m_tlnn(w, IW_I) != 0); + if (cpu.mr_inpxct && (cpu.mr_inpxct & (src ? 02 : 01))) { + /* Use XBEA if source, XBRW if dest, plus PCS */ + err = xea_fiwcalc(w, pag_pcsget(), + (src ? cpu.acblk.xbea : cpu.acblk.xbrw), + (src ? cpu.vmap.xbea : cpu.vmap.xbrw), + &(bp->y), EA_ARG_IFIW); + } else + err = xea_fiwcalc(w, PC_SECT, + cpu.acblk.xbea, cpu.vmap.xbea, + &(bp->y), EA_ARG_IFIW); + + } else { /* EFIW, no default section needed */ + bp->fmt = BPF_TWG; /* Say originally a TWGBP */ + bp->isindir = (op10m_tlnn(w, IW_EI) != 0); + if (cpu.mr_inpxct && (cpu.mr_inpxct & (src ? 02 : 01))) { + err = xea_fiwcalc(w, 0, + (src ? cpu.acblk.xbea : cpu.acblk.xbrw), + (src ? cpu.vmap.xbea : cpu.vmap.xbrw), + &(bp->y), EA_ARG_EFIW); + } else + err = xea_fiwcalc(w, 0, + cpu.acblk.xbea, cpu.vmap.xbea, + &(bp->y), EA_ARG_EFIW); + } + if (err) + return (enum xires)err; + } + else + goto localbp; /* Not a OWGBP or TWGBP, drop thru */ + + } else { + /* Plain vanilla one-word local BP. + ** As for TWGBPs, default section is PC section. + */ + localbp: + bp->s = (LHGET(w) >> 6) & 077; /* S in next 6 */ + bp->fmt = BPF_OWL; /* Say originally an OWLBP */ + bp->isindir = (op10m_tlnn(w, IW_I) != 0); + + if (cpu.mr_inpxct && (cpu.mr_inpxct & (src ? 02 : 01))) { + /* Use XBEA if source, XBRW if dest, plus PCS */ + err = xea_fiwcalc(w, pag_pcsget(), + (src ? cpu.acblk.xbea : cpu.acblk.xbrw), + (src ? cpu.vmap.xbea : cpu.vmap.xbrw), + &(bp->y), EA_ARG_IFIW); + } else { + /* Use XBEA mapping plus PC section */ + err = xea_fiwcalc(w, PC_SECT, + cpu.acblk.xbea, cpu.vmap.xbea, + &(bp->y), EA_ARG_IFIW); + } + if (err) + return (enum xires)err; + } + +#else /* end EXTADR */ + + bp->s = (LHGET(w) >> 6) & 077; /* S in next 6 */ + bp->isindir = (op10m_tlnn(w, IW_I) != 0); + + err = ((cpu.vmap.cur == cpu.vmap.xea) + ? ea_fiwcalc(w, cpu.acblk.xea, cpu.vmap.xea, &(bp->y)) + : (src ? ea_fiwcalc(w, cpu.acblk.xbea, cpu.vmap.xbea, &(bp->y)) + : ea_fiwcalc(w, cpu.acblk.xbrw, cpu.vmap.xbrw, &(bp->y)))); + if (err) + return (enum xires)err; +#endif /* !EXTADR */ + + /* Have P, S, and Y. Now finish setting up handy variables. */ + bp->newp = (W10BITS - bp->s) & 077; /* New P when moving to next word */ + bp->bmsk = /* Byte mask to use */ + wbytemask[bp->s <= W10BITS ? bp->s : W10BITS]; + bp->vp = NULL; + bp->ycnt = 0; + + return RES_OK; +} + + +/* XBPUPDATE - Store BP back in AC. +** Format to store is pre-determined by initial conversion. See +** XBPGET for the rules. +** NOTE: Care must be taken to preserve existing S, I, and X fields; +** only P and Y can be updated. For Y in particular this means +** updating it with the # of word increments since the last update. +*/ +static void +xbpupdate(register struct cleanbp *bp) +{ + register w10_t w; + register int ac = bp->ac; + +#if KLH10_EXTADR + switch (bp->fmt) { + case BPF_OWL: +#endif + w = ac_get(ac); + op10m_tlz(w, 0770000); /* Clear for new P */ + op10m_tlo(w, ((h10_t)(bp->p) << 12)); /* Set new P */ + RHSET(w, (RHGET(w) + bp->ycnt)&H10MASK); /* Set new Y */ + ac_set(ac, w); +#if KLH10_EXTADR + break; + + case BPF_TWL: + w = ac_get(ac); + op10m_tlz(w, 0770000); /* Clear for new P */ + op10m_tlo(w, ((h10_t)(bp->p) << 12) | BPF_2WD); + ac_set(ac, w); + ac = ac_off(ac, 1); + w = ac_get(ac); + RHSET(w, (RHGET(w) + bp->ycnt)&H10MASK); /* Set new Y */ + ac_set(ac, w); /* Store 2nd wd */ + break; + + case BPF_OWG: /* Store as simple TWG */ + XWDSET(w, (((h10_t)(bp->p) << 12) | (bp->s << 6) | BPF_2WD), 0); + ac_set(ac, w); + XWDSET(w, (h10_t)va_sect(bp->y), va_insect(bp->y)); + ac_set(ac_off(ac,1), w); /* Store 2nd wd */ + break; + + case BPF_TWG: + w = ac_get(ac); + op10m_tlz(w, 0770000); /* Clear for new P */ + op10m_tlo(w, ((h10_t)(bp->p) << 12) | BPF_2WD); + ac_set(ac, w); + ac = ac_off(ac, 1); + w = ac_get(ac); + { + uint32 y30; + y30 = (va_30frword(w) + bp->ycnt) & MASK30; /* Get new 30-bit Y */ + XWDSET(w, ((LHGET(w)&0770000) /* Preserve high 6 bits */ + | (y30 >> 18)), y30 & H10MASK); + } + ac_set(ac, w); /* Store 2nd wd */ + break; + } +#endif /* KLH10_EXTADR */ + bp->ycnt = 0; /* Must clear so further updates will work! */ +} + + +/* XIBP - Increment clean pointer, return TRUE if word address bumped */ +/* static int xibp(bp); */ +#define xibp(bp) ( \ + (((bp)->p -= (bp)->s) < 0) \ + ? ((bp)->p = (bp)->newp, (bp)->ycnt++, va_inc((bp)->y), 1) \ + : 0 ) + +/* XDECBP - Back up one, after already incremented. +** This is a function rather than an in-line macro because it's +** only called when backing out of an error, i.e. rarely. +** This should never be called unless the BP has been incremented, +** meaning that backing up should always be possible simply by +** backing up P. +** Unfortunately, nothing keeps the user from providing a bogus S. +** Unclear what the real machine does, but here I'll try to recover. +*/ +static void +xdecbp(register struct cleanbp *bp) +{ + if ((bp->p += bp->s) > W10BITS) { +#if 0 + panic("xdecbp: bad P! P=%lo S=%lo", (long)bp->p, (long)bp->s); +#endif + bp->p = bp->newp; /* Attempt feeble recovery */ + } +} + +/* XILDB - Fetch source byte. +** Note: for support of PXCT'd MOVSLJ, the map used is XBEA which +** is interpreted by EXTEND as the "source" map. +** ALSO NOTE: In order to conform to expectations of DFKCC diagnostic, +** the byte pointer is always incremented, whether or not a page failure +** happens. If a page failure does happen, the backup simply adjusts +** P, and leaves Y alone; if Y was incremented, it is left incremented. +** This is guaranteed to still work since P will then point to start of +** word and the next increment will get us back to the page fail point. +** ALSO NOTE: in order to accurately emulate the screw case where +** the BP is using an indirect address, we need to remember this fact +** and recompute the byte EA entirely when Y is incremented. Ugh! +** Worst problem is that all normal EA computations +** do a pagfail longjmp, with no provision for a failure return! +** Thus we use a duplicate XEA calc that *does* do fail-return. +** NOTE: also needs to provide for INSBRK exit! Yuck! +*/ + +static enum xires +xildb(register struct cleanbp *bp, register w10_t *wp) +{ + register w10_t w; + + if ((bp->p -= bp->s) < 0) { /* Find new P and Y */ + bp->p = bp->newp; /* Next word, so fix up P */ + bp->vp = NULL; + bp->ycnt++; + if (bp->isindir) { /* Using indirect EA? */ + /* Ugh. Write out BP, then re-compute byte EA. */ + register enum xires res; + xbpupdate(bp); + if (res = xbpinit(bp, bp->ac, bp->src)) { + xdecbp(bp); /* Failed?? Back up P */ + return bp->err = res; + } + } + else + va_inc(bp->y); /* Can just bump Y */ + } + if (!bp->vp) { /* Map new word addr if needed */ + if (!(bp->vp = vm_xbeamap(bp->y, VMF_READ|VMF_NOTRAP))) { + /* Map failure only backs up P, never Y! */ + if ((bp->p += bp->s) > W10BITS) /* Back up P */ + bp->p = bp->newp; /* Catch any screwy P+S case */ + return bp->err = RES_PF; /* Report failure */ + } + bp->wd = vm_pget(bp->vp); /* Fetch new word */ + } + + w = bp->wd; + op10m_rshift(w, bp->p); /* Shift word to right-align byte */ + op10m_and(w, bp->bmsk); /* Mask out byte */ + *wp = w; + return RES_OK; +} + + +/* XIDPB - Deposit dest byte. +** Note: for support of PXCT'd MOVSLJ, the map used is XBRW which +** is interpreted by EXTEND as the "destination" map. +** Same behavior on page failure as XILDB above. +*/ +static enum xires +xidpb(register struct cleanbp *bp, register w10_t *wp) +{ + register w10_t w, bmask; + + if ((bp->p -= bp->s) < 0) { /* Find new P and Y */ + bp->p = bp->newp; /* Next word, so fix up P */ + bp->vp = NULL; + bp->ycnt++; + if (bp->isindir) { /* Using indirect EA? */ + /* Ugh. Write out BP, then re-compute byte EA. */ + register enum xires res; + xbpupdate(bp); + if (res = xbpinit(bp, bp->ac, bp->src)) { + xdecbp(bp); /* Failed?? Back up P */ + return bp->err = res; + } + } + else + va_inc(bp->y); /* Can just bump Y */ + } + if (!bp->vp) { /* Map new word addr if needed */ + if (!(bp->vp = vm_xbrwmap(bp->y, VMF_WRITE|VMF_NOTRAP))) { + /* Map failure only backs up P, never Y! */ + if ((bp->p += bp->s) > W10BITS) /* Back up P */ + bp->p = bp->newp; /* Catch any screwy P+S case */ + return bp->err = RES_PF; /* Report failure */ + } + bp->wd = vm_pget(bp->vp); /* Fetch new word */ + } + + bmask = bp->bmsk; /* Get mask for byte */ + w = *wp; /* Fetch new byte */ + op10m_lshift(bmask, bp->p); /* Shift out to align mask with byte pos */ + op10m_lshift(w, bp->p); /* Shift new byte into position */ + op10m_andcm(bp->wd, bmask); /* Clear bits from byte pos in word */ + op10m_and(w, bmask); /* And remove excess bits from byte */ + op10m_ior(bp->wd, w); /* Stuff byte into word! */ + + /* Later try to defer storage until all done with word */ + vm_pset(bp->vp, bp->wd); /* Store new word */ + return RES_OK; +} + + +/* This is used only for MOVSRJ. +** The KL+KS ucode simply IBPs to effect the MOVSRJ skipping. +** This does it more cleverly, but the real reason is so the EA can +** be correctly recomputed if the BP was indirected through. +** Any resulting page fault happens before the first actual ILDB ref +** but that's okay. +** Note that no CLOCKPOLL/INSBRKTEST is needed because the loop here is +** limited to 35 iterations or less. +*/ +static enum xires +xadjbp(register struct cleanbp *bp, + register int32 n) /* Positive # of bytes to adjust up by */ +{ + register int32 yn; + + /* Make sure can't screw up due to bogus S (zero or >36) */ + if (bp->s <= 0) + return RES_OK; + + while (--n >= 0) { + if ((bp->p -= bp->s) < 0) { /* Find new P until hit end */ + + /* Must increment Y by 1. Take advantage of the forced word + alignment to add all remaining words in one swoop. + */ + if (bp->s >= W10BITS) { /* Guard against bogus S */ + yn = n+1; /* Full-word bytes. Note extra 1 */ + n = 0; + } else { + yn = (n / (W10BITS/bp->s)) + 1; /* Note extra 1 */ + n = (n % (W10BITS/bp->s)); + } + bp->ycnt += yn; + bp->p = bp->newp - (n * bp->s); + + /* Now update Y. If BP is indirect, must recompute the EA and + possibly fail (which involves no backup). + */ + if (bp->isindir) { + xbpupdate(bp); + return xbpinit(bp, bp->ac, bp->src); + } else + va_add(bp->y, yn); + break; + } + } + return RES_OK; +} + +/* MOVSLJ - Move String Left Justified (EXTEND [016 ]) +** +** This is the only EXTEND string instruction legal for PXCTing (and only +** on an extended KL). The +** necessary mappings are done by the byte-pointer auxiliary routines. +** See DEC PRM p.3-51. +*/ +xinsdef(ix_movslj) +{ + register int32 len1, len2; /* Note signed */ + register vaddr_t va; + struct cleanbp s1, s2; + w10_t wbyte; + w10_t wfill; + enum xires res = RES_TRUNC; + + AC_32GET(len1, ac, 0, 0777000); /* Get source len */ + AC_32GET(len2, ac, 3, 0777000); /* Get dest len */ + + /* Now to satisfy diag expectations, always fetch fill byte, thus + ** triggering a page fail at this point if necessary. + */ + va = e0; + va_inc(va); /* Get E0+1 */ + wfill = vm_read(va); /* Get c(E0+1) */ + + if (!len2) /* Quick check for null case */ + return len1 ? PCINC_1 : PCINC_2; /* Skip if no source bytes left */ + + XBPGET(&s1, ac, 1, BPSRC); /* Set up for read, may pagefault */ + XBPGET(&s2, ac, 4, BPDST); + + /* Default result depends on whether strings will count out together */ + res = (len1 == len2) ? RES_WON : RES_TRUNC; + + while (--len2 >= 0) { + if (--len1 < 0) { /* If source string is gone */ + /* Get fill byte, start inner loop */ + res = RES_WON; /* Source string exhausted */ + do { + if (xidpb(&s2, &wfill)) { + /* Clean up and fail (normally page fail) */ + res = s2.err; + ++len2; + break; + } + } while (--len2 >= 0); + break; /* Return, either won or page-failed */ + } + + /* Fetch & store byte */ + if (xildb(&s1, &wbyte)) { + /* Clean up and fail (normally page fail) */ + res = s1.err; + ++len1, ++len2; /* Back up */ + break; + } + if (xidpb(&s2, &wbyte)) { /* Store byte */ + /* Clean up and page-fail */ + res = s2.err; + xdecbp(&s1); /* Back up source BP */ + ++len1, ++len2; + break; + } + + /* Done with one iteration, now check before doing next */ + if (len2 <= 0) + break; /* Nope, all done */ + CLOCKPOLL(); /* More left, keep clock going */ + if (INSBRKTEST()) { /* Watch for PI interrupt */ + res = RES_PI; + break; + } + } + + /* Done, update ACs and return appropriate PC increment + ** (Skips if source was exhausted) + */ + ac_32set(ac, (len1 > 0 ? len1 : 0)); + xbpupdate(&s1); /* Update BP in AC+1,2 */ + ac_32set(ac_off(ac,3), (len2 > 0 ? len2 : 0)); + xbpupdate(&s2); /* Update BP in AC+4,5 */ + + switch (res) { + case RES_PF: pag_fail(); /* Never returns */ + case RES_PI: apr_int(); /* Never returns */ + case RES_WON: return PCINC_2; /* Skips if source exhausted */ + default: + case RES_TRUNC: return PCINC_1; + } +} + +/* MOVSO - Move String Offset (EXTEND [014 ]) +*/ +xinsdef(ix_movso) +{ + register int32 len1, len2; /* Note signed */ + register vaddr_t va; + struct cleanbp s1, s2; + w10_t wbyte; + w10_t wfill; + register w10_t woff, wmask; + enum xires res = RES_TRUNC; + + if (va_insect(e1) & H10SIGN) /* See if high halfwd bit set */ + LRHSET(woff, H10MASK, va_insect(e1)); /* Yep, sign-extend it */ + else LRHSET(woff, 0, va_insect(e1)); /* Nope */ + + AC_32GET(len1, ac, 0, 0777000); /* Get source len */ + AC_32GET(len2, ac, 3, 0777000); /* Get dest len */ + + /* Now to satisfy diag expectations, always fetch fill byte, thus + ** triggering a page fail at this point if necessary. + */ + va = e0; + va_inc(va); /* Get E0+1 */ + wfill = vm_read(va); /* Get c(E0+1) */ + + if (!len2) /* Quick check for null case */ + return len1 ? PCINC_1 : PCINC_2; /* Skip if no source bytes left */ + XBPGET(&s1, ac, 1, BPSRC); /* Set up for read, may pagefault */ + XBPGET(&s2, ac, 4, BPDST); + + /* Final setup for offset - remember mask to use */ + wmask = s2.bmsk; /* Get mask for byte */ + op10m_setcm(wmask); /* Complement it to get test mask */ + + /* Default result depends on whether strings count out together */ + res = (len1 == len2) ? RES_WON : RES_TRUNC; + + while (--len2 >= 0) { + if (--len1 < 0) { /* If source string is gone */ + + /* Note offset not applied to fill byte! */ + res = RES_WON; /* Source string exhausted */ + do { + if (xidpb(&s2, &wfill)) { + /* Clean up and page fail */ + res = s2.err; + ++len2; + break; + } + } while (--len2 >= 0); + break; /* Return, either won or page-failed */ + } + + /* Fetch & store byte */ + if (xildb(&s1, &wbyte)) { + /* Clean up and page-fail */ + res = s1.err; + ++len1, ++len2; /* Back up */ + break; + } + /* Now do special MOVSO stuff. */ + op10m_add(wbyte, woff); /* Add offset */ + if (op10m_tdnn(wbyte, wmask)) { /* Check for non-byte bits set */ + /* Clean up and lose. Source BP and len are left pointing + ** to the guilty byte (last one referenced). + */ + ++len2; /* Back up pre-decremented count */ + res = RES_TRUNC; + break; + } + if (xidpb(&s2, &wbyte)) { /* Store byte */ + /* Clean up and page-fail */ + res = s2.err; + xdecbp(&s1); /* Back up source BP */ + ++len1, ++len2; + break; + } + + /* Done with one iteration, now check before doing next */ + if (len2 <= 0) /* More to do? */ + break; + CLOCKPOLL(); /* More left, keep clock going */ + if (INSBRKTEST()) { /* Watch for PI interrupt */ + res = RES_PI; + break; + } + } + + /* Done, update ACs and return appropriate PC increment + ** (Skips if source was exhausted) + */ + ac_32set(ac, (len1 > 0 ? len1 : 0)); + xbpupdate(&s1); /* Update BP in AC+1,2 */ + ac_32set(ac_off(ac,3), (len2 > 0 ? len2 : 0)); + xbpupdate(&s2); /* Update BP in AC+4,5 */ + + switch (res) { + case RES_PF: pag_fail(); /* Never returns */ + case RES_PI: apr_int(); /* Never returns */ + case RES_WON: return PCINC_2; /* Skips if source exhausted */ + default: + case RES_TRUNC: return PCINC_1; + } +} + +/* MOVST - Move String Translated (EXTEND [015 ]) +** +** It appears that real machine always fetches the fill byte regardless +** of whether it is needed or not. So I'm doing the same thing in order +** to accurately emulate the page fail testing done by the diagnostics. +** Sigh. Similar problem for CMPSxx. +*/ + +xinsdef(ix_movst) +{ + register int32 len1, len2; /* Note signed */ + register vmptr_t vp; + register vaddr_t va; + h10_t flags; + struct cleanbp s1, s2; + w10_t wbyte; + w10_t wfill; + enum xires res = RES_WON; + + /* First get lengths and check for illegal bits */ + AC_32GET(len1, ac, 0, 0077000); /* Get source len */ + flags = ac_getlh(ac) & CVTF_ALL; /* Find flags */ + + AC_32GET(len2, ac, 3, 0777000); /* Get dest len */ + + /* Now to satisfy diag expectations, always fetch fill byte, thus + ** triggering a page fail at this point if necessary. + */ + va = e0; + va_inc(va); /* Get E0+1 */ + wfill = vm_read(va); /* Get c(E0+1) */ + + if (!len2) /* Quick check for no-destination case */ + return len1 ? PCINC_1 : PCINC_2; /* Skip if no source bytes either */ + XBPGET(&s1, ac, 1, BPSRC); /* Set up for read, may pagefault */ + XBPGET(&s2, ac, 4, BPDST); + + /* Start loop. Note result defaults to RES_WON. */ + while (--len1 >= 0) { /* Loop reading from source */ + register h10_t ent; + if (xildb(&s1, &wbyte)) { /* Fetch byte */ + res = s1.err; /* Clean up and page-fail */ + ++len1; /* Back up */ + break; + } + /* Now do special MOVST stuff. */ + ent = RHGET(wbyte); + va = e1; + va_add(va, (ent>>1)); /* Get E1+(ent>>1) */ + if (!(vp = vm_xrwmap(va, VMF_READ|VMF_NOTRAP))) { + xdecbp(&s1); /* Back up source bp */ + ++len1; /* Back up */ + res = RES_PF; /* Clean up and page-fail */ + break; + } + /* Do translation (same table as CVTDBT) */ + ent = (ent & 01) ? vm_pgetrh(vp) : vm_pgetlh(vp); + switch ((ent >> 15) & 07) { + case 0: break; + case 1: res = RES_TRUNC; break; + case 2: flags &= ~CVTF_M; break; + case 3: flags |= CVTF_M; break; + case 4: flags |= CVTF_S|CVTF_N; break; + case 5: flags |= CVTF_N; res = RES_TRUNC; break; + case 6: flags = CVTF_S|CVTF_N; break; + case 7: flags = CVTF_S|CVTF_N|CVTF_M; break; + } + if (res != RES_WON) /* If wants to stop now, */ + break; /* leave the loop! */ + + if (flags & CVTF_S) { + LRHSET(wbyte, 0, ent & 007777); /* Get translated byte */ + if (xidpb(&s2, &wbyte)) { /* Deposit it! */ + res = s2.err; /* Clean up and page-fail */ + xdecbp(&s1); + ++len1; +/* KLH: Possible bug here, if pagefail at this point + the flags have already been clobbered! But flag state shouldn't + affect the repeated translation, so this should be OK. +*/ + break; + } + if (--len2 <= 0) { /* Byte stored, bump count! */ + if (len1 > 0) /* Dest full, must stop. */ + res = RES_TRUNC; /* Say truncated, if any source left */ + break; + } + } + + /* Done with one iteration, now check before doing next */ + if (len1 <= 0) /* Stop now if none left */ + break; + CLOCKPOLL(); /* More left, keep clock going */ + if (INSBRKTEST()) { /* Watch for PI interrupt */ + res = RES_PI; + break; + } + } + + /* Now see if have to fill out rest of dest string. + ** This only happens if result is still RES_WON. + */ + if (res == RES_WON && len2 > 0) { + for (;;) { + /* OK, set up for fill loop, using saved fill byte */ + /* Note translation not applied to fill byte! */ + if (xidpb(&s2, &wfill)) { + res = s2.err; /* Clean up and page fail */ + break; + } + if (--len2 <= 0) + break; + CLOCKPOLL(); /* More left, keep clock going */ + if (INSBRKTEST()) { /* Watch for PI interrupt */ + res = RES_PI; + break; + } + } + } + + /* Done, update ACs and return appropriate PC increment + ** (Skips if source was exhausted) + */ + if (len1 < 0) + len1 = 0; /* Clear so don't return -1! */ + ac_setlrh(ac, flags | (len1>>H10BITS), len1 & H10MASK); + xbpupdate(&s1); /* Update BP in AC+1,2 */ + ac_32set(ac_off(ac,3), (len2 > 0 ? len2 : 0)); + xbpupdate(&s2); /* Update BP in AC+4,5 */ + + switch (res) { + case RES_PF: pag_fail(); /* Never returns */ + case RES_PI: apr_int(); /* Never returns */ + case RES_WON: return PCINC_2; /* Skips if source exhausted */ + default: + case RES_TRUNC: return PCINC_1; + } +} + +/* MOVSRJ - Move String Right Justified (EXTEND [017 ]) +** +** Although PRM doesn't say so explicitly, AC is always 0 unless the +** instr is interrupted, because the source string is always completely +** consumed one way or another. +*/ +xinsdef(ix_movsrj) +{ + register int32 len1, len2; /* Note signed */ + register vaddr_t va; + struct cleanbp s1, s2; + w10_t wbyte; + w10_t wfill; + enum xires res = RES_OK; + + AC_32GET(len1, ac, 0, 0777000); /* Get source len */ + AC_32GET(len2, ac, 3, 0777000); /* Get dest len */ + + /* Now to satisfy diag expectations, always fetch fill byte, thus + ** triggering a page fail at this point if necessary. + */ + va = e0; + va_inc(va); /* Get E0+1 */ + wfill = vm_read(va); /* Get c(E0+1), may pagefault */ + + if (!len2) /* Quick check for null dest case */ + return PCINC_2; /* MOVSRJ always skips?? */ + XBPGET(&s1, ac, 1, BPSRC); /* Set up for read, may pagefault */ + XBPGET(&s2, ac, 4, BPDST); + + /* Decide what to do */ + if (len1 < len2) { + /* Use initial padding with fill. Fill dest until same len as src. */ + for (;;) { + if (xidpb(&s2, &wfill)) { + res = s2.err; + break; + } + if (--len2 <= len1) /* Update dest len, keep going */ + break; /* unless dest now small enough */ + + CLOCKPOLL(); /* More to do, keep clock going */ + if (INSBRKTEST()) { /* Watch for PI interrupt */ + res = RES_PI; + break; + } + } + } else if (len1 > len2) { + /* Source too big, skip over source bytes before starting copy */ + res = xadjbp(&s1, len1-len2); /* IBP it cleverly, RES_OK if won */ + len1 = len2; /* Source len now same as dest */ + } + + if ((res == RES_OK) /* Make sure still OK (not PF or PI) */ + && len2) { /* and dest string still needs more stuff */ + /* At this point, len1 == len2 and at least one byte to copy */ + for (;;) { + if (xildb(&s1, &wbyte)) { /* Fetch byte! */ + res = s1.err; /* Page-fail. No backup needed */ + break; + } + if (xidpb(&s2, &wbyte)) { /* Store byte! */ + res = s2.err; + xdecbp(&s1); /* Page-fail, must back up source BP */ + break; + } + + /* Done with one iteration, now check before doing next */ + if (--len2 <= 0) + break; /* Nope, all done */ + CLOCKPOLL(); /* More left, keep clock going */ + if (INSBRKTEST()) { /* Watch for PI interrupt */ + res = RES_PI; + break; + } + } + len1 = len2; /* Update len1 to match len2 */ + } + + /* Done, update ACs and return appropriate PC increment + ** (MOVSRJ always skips unless interrupted) + */ + ac_32set(ac, len1); + xbpupdate(&s1); /* Update BP in AC+1,2 */ + ac_32set(ac_off(ac,3), len2); + xbpupdate(&s2); /* Update BP in AC+4,5 */ + + switch (res) { + case RES_PF: pag_fail(); /* Never returns */ + case RES_PI: apr_int(); /* Never returns */ + default: + case RES_OK: + case RES_WON: return PCINC_2; + } +} + +/* CMPSxx - Compare Strings (EXTEND [001-007 ]) +** Note 000 and 004, which otherwise would be CMPS and CMPSA, +** are respectively illegal and EDIT. +** +** In order to make the DFKCC diag happy it appears that we need to +** reference the appropriate fill word at E0+1 or E0+2 first, before +** comparing any bytes! +*/ + +/* Success bits for op */ +#define CMPF_LT 01 /* Skip if S1 < S2 */ +#define CMPF_EQ 02 /* Skip if S1 = S2 */ +#define CMPF_GT 04 /* Skip if S1 > S2 */ + +static int cmpftab[] = { + 0, /* [000 ] Illegal - wd be "CMPS", never skip */ + CMPF_LT, /* [001 ] CMPSL */ + CMPF_EQ, /* [002 ] CMPSE */ + CMPF_LT|CMPF_EQ, /* [003 ] CMPSLE */ + -1, /* [004 ] EDIT - wd be "CMPSA", always skip */ + CMPF_EQ|CMPF_GT, /* [005 ] CMPSGE */ + CMPF_LT|CMPF_GT, /* [006 ] CMPSN */ + CMPF_GT /* [007 ] CMPSG */ +}; + +/* Common code for CMPSx extended instructions. +** Skips if string comparison result matches any of the requested CMPF bits. +*/ +xinsdef(ix_cmps) /* Do all CMPSx instructions */ +{ + register int32 len1, len2; /* Note signed */ + register vaddr_t va; + struct cleanbp s1, s2; + w10_t w1, w2; + register w10_t fill; + enum xires res = RES_OK; + register int cmpf; + + /* Set up string lengths. */ + AC_32GET(len1, ac, 0, 0777000); /* Get source len */ + AC_32GET(len2, ac, 3, 0777000); /* Get dest len */ + + /* To accurately emulate the KS/KL with respect to page fails, we + ** we need to fetch the appropriate fill byte first, and do it even + ** if none is needed (string lengths equal). + */ +#if 0 /* More efficient, but won't trigger failures that diag expects */ + if (!len1 && !len2) /* Quick check for null case */ + return (xop == (IX_CMPSE & 0777)) ? PCINC_2 : PCINC_1; +#endif + + /* It appears that when the lengths are equal, the fill byte fetched + ** comes from E0+2 on the KS, E0+1 on the KL. Whoopee. + ** Again, this is only to keep diags happy. + */ + va = e0; /* Set up E0 */ +#if KLH10_CPU_KL + if (len1 <= len2) +#elif KLH10_CPU_KS + if (len1 < len2) +#endif + va_inc(va); /* Get E0+1 (source fill) */ + else + va_add(va, 2); /* Get E0+2 (dest fill) */ + fill = vm_read(va); /* Fetch fill byte, may pagefault */ + + XBPGET(&s1, ac, 1, BPSRC); /* Set up for read, may pagefault */ + XBPGET(&s2, ac, 4, BPDST); + + /* Loop until pagefail, interrupt, bytes not equal, or count out */ + cmpf = CMPF_EQ; /* Default if count out is equal */ + for (;;) { + if (--len1 >= 0) { /* Get byte from string 1 */ + if (xildb(&s1, &w1)) { + res = s1.err; + ++len1; /* Back up, assume BP backed up */ + break; + } + } else { + if (len2 <= 0) break; + if (len1 == -1) { /* If first time here, get fill byte */ + w1 = fill; + } + } + + if (--len2 >= 0) { /* Get byte from string 2 */ + if (xildb(&s2, &w2)) { + res = s2.err; + ++len2; /* Back up */ + if (len1 >= 0) { + ++len1; + xdecbp(&s1); /* Back up first BP */ + } + break; + } + } else { + /* No test for len1 cuz if we got here it's known to be OK */ + if (len2 == -1) { /* If first time here, get fill byte */ + w2 = fill; + } + } + + /* Both bytes fetched, now test */ + if (op10m_camn(w1, w2)) { /* Test for equality */ + cmpf = (op10m_ucmpl(w1, w2) /* Not equal! Use unsigned compare */ + ? CMPF_LT : CMPF_GT); + break; + } + + /* Equal so far. Set up to repeat loop... */ + CLOCKPOLL(); /* Keep clock going */ + if (INSBRKTEST()) { /* Watch for PI interrupt */ + res = RES_PI; + break; + } + } + + /* Return result after updating ACs */ + ac_32set(ac, (len1 > 0 ? len1 : 0)); + xbpupdate(&s1); /* Update BP in AC+1 */ + ac_32set(ac_off(ac,3), (len2 > 0 ? len2 : 0)); + xbpupdate(&s2); /* Update BP in AC+4 */ + + switch (res) { + case RES_PF: pag_fail(); /* Never returns */ + case RES_PI: apr_int(); /* Never returns */ + default: + /* Skip if result bit found in op's success bits */ + return (cmpf & cmpftab[xop & 07]) ? PCINC_2 : PCINC_1; + } +} + +/* CVTBDO - Convert Binary to Decimal Offset (EXTEND [012 ]) + CVTBDT - Convert Binary to Decimal Translated (EXTEND [013 ]) + +NOTE: + It has been discovered the hard way that CVTBDx does the +string length check before it attempts to load or reference the byte +pointers (in particular, during monitor startup to determine CPU type). +Hence the initialization of S2 must be delayed until after that point. + + Also, the fill byte fetch, unlike the MOVS and CMPS instructions, +is only done if needed -- not beforehand! + + Also, the real hardware uses the First-Part-Done flag to tell +itself that an interrupted CVTBDx has gubbish in the ACs that are not +meaningful to anything but the KL microcode. The emulation here avoids +using FPD by always restoring the ACs to a sensible state. For this reason, +diagnostics (ie DFKCC) that test interrupted results will always fail. + + Note that a successful CVTBDx is defined to clear the length AC, +even if no filling was done; thus the returned length cannot be used to +tell how many characters were deposited, and in fact its value during +an interrupt (on a real KL) is the # of digits left to do, not the # of +bytes left in destination buffer. The code here emulates this for +simplicity. + +*/ +static int cvtb_digcnt(dw10_t *ad); + +xinsdef(ix_cvtbd) /* Do both CVTBDO and CVTBDT */ +{ + register int32 len2; /* Note signed */ + register vaddr_t va; + h10_t flags; + register int ndigs; + struct cleanbp s2; + w10_t wfill, woff; + dw10_t d; + int transf; + enum xires res = RES_TRUNC; + + AC_32GET(len2, ac, 3, 0077000); /* Get dest len (may fail) */ + flags = ac_getlh(ac_off(ac,3)) & CVTF_ALL; /* Find flags */ + + /* Check opcode to see if doing translate. Must mask IX_ since enum + ** has special high bit(s). + */ + transf = (xop == (IX_CVTBDT & 0777)); /* See if doing translate */ + if (!transf) { /* If not, set up offset */ + if (va_insect(e1) & H10SIGN) /* See if high halfwd bit set */ + LRHSET(woff, H10MASK, va_insect(e1)); /* Yep, sign-extend it */ + else LRHSET(woff, 0, va_insect(e1)); /* Nope */ + } + + ac_dget(ac, d); /* Get binary double */ + + /* Set N,M flags appropriately */ + if (op10m_signtst(d.w[0])) { + flags |= CVTF_N | CVTF_M; /* Non-zero and Minus */ + op10m_dmovn(d); /* Make positive */ + } else { + op10m_signclr(d.w[1]); /* Positive, ensure low sign clear */ + if (op10m_skipn(d.w[0]) || op10m_skipn(d.w[1])) + flags |= CVTF_N; /* Non-zero */ + } + /* Put flags in now if KL -- easier than checking for aborts at + ** XBPGET and fill-byte fetch below. + */ +#if KLH10_CPU_KL + ac_setlrh(ac_off(ac,3), flags | (len2>>H10BITS), len2 & H10MASK); +#endif + + /* The low sign bit is guaranteed to be clear at this point, + ** and the high sign bit will only be set if the entire number + ** is the maximum negative value of -2^70. + */ + + /* At this point, determine how large number will be. + ** Can either do a binary search on power-of-10 table, or + ** actually build string and find out. + ** Max is 22 digits; search would take max of 5 comparisons (2^5 == 32) + ** whereas building string does divides by 10... ugh! + ** Idea: faster to do own add/sub loop, using power-of-10 table. A + ** single regular DDIV does 70 dadd/dsubs! + */ + if (op10m_signtst(d.w[0])) { /* If still negative, */ + ndigs = 22; /* is max # of 2^70!! */ + } else { + ndigs = cvtb_digcnt(&d); + } + + if (ndigs > len2) { + /* Number requires more digits than dest string has, so fail */ + return PCINC_1; + } + + /* OK to proceed, now set up destination BP. + ** This cannot be done earlier since CVTBDx is used by certain + ** processor-ID algorithms that leave garbage in the ACs and will be + ** very surprised to get a page fail or MUUO instead! + */ + XBPGET(&s2, ac, 4, BPDST); /* Set up dest BP from ACs */ + + /* If dest string is longer, and CVTF_L is set, pad out. */ + if ((len2 > ndigs) && (flags & CVTF_L)) { + + /* Get fill byte word from E0+1 - can page-fail! + ** No special cleanup needed as flags have already been set if KL. + */ + va = e0; + va_inc(va); + wfill = vm_read(va); + + res = RES_TRUNC; + while (len2 > ndigs) { + if (xidpb(&s2, &wfill)) { /* Store fill byte */ + res = s2.err; + break; + } + --len2; + CLOCKPOLL(); /* Keep clock going */ + if (INSBRKTEST()) { /* Watch for PI interrupt */ + res = RES_PI; + break; + } + } + if (res != RES_TRUNC) { + ac_setlrh(ac_off(ac,3), flags | (len2>>H10BITS), len2 & H10MASK); + xbpupdate(&s2); /* Update BP in AC+4 */ + switch (res) { + case RES_PF: pag_fail(); + case RES_PI: apr_int(); + default: break; + } + } + } + + /* At this point, only "ndigs" more bytes to deposit. + ** OK to stop keeping track of len2, because returned count is + ** always 0 if success, and if interrupted the real KL sets this + ** to updated ndigs, not updated len2, so we do the same. + ** ndigs is guaranteed to be nonzero (see cvtb_digcnt). + */ + + /* Build decimal string. Starting with power-of-10 specified by + ** ndigs, count how many times one has to subtract that before + ** number is within range of lesser power, and that's the digit + ** for that position. + */ + for (;;) { + register int dig; + w10_t wbyte; + dw10_t rbdw; /* For rollback if pagefail */ + + rbdw = d; /* Save current number */ + if (ndigs == 1) + dig = RHGET(d.w[1]); /* d contains last digit */ + else + for (dig = 0; !op10m_udcmpl(d, dpow10[ndigs-1]); ++dig) { + /* Carry out a DSUB. Assumes low sign bit clear! */ + op10m_sub(d.w[0], dpow10[ndigs-1].w[0]); /* Sub high words */ + op10m_sub(d.w[1], dpow10[ndigs-1].w[1]); /* Sub low words */ + if (op10m_signtst(d.w[1])) { /* If low sign now set, */ + op10m_dec(d.w[0]); /* carry to high word */ + op10m_signclr(d.w[1]); /* and clear low sign */ + } + } + + /* dig now contains digit 0-9 for this position */ + + /* Offset or translate digit into a byte, then deposit that */ + if (!transf) { + wbyte = woff; + op10m_addi(wbyte, dig); + } else { + /* Translate stuff */ + register vmptr_t vp; + va = e1; + va_add(va, dig); /* Get E1+dig */ + if (!(vp = vm_xrwmap(va, VMF_READ|VMF_NOTRAP))) { + res = RES_PF; /* Page fail, can't fetch transl */ + d = rbdw; /* Roll back to original # */ + break; + } + wbyte = vm_pget(vp); + if (ndigs == 1 && (flags & CVTF_M)) /* Special hack for last dig */ + RHSET(wbyte, LHGET(wbyte)); /* Use LH instead if M set */ + LHSET(wbyte, 0); /* Clear LH */ + } + if (xidpb(&s2, &wbyte)) { + res = s2.err; /* Page fail, can't deposit byte */ + d = rbdw; /* Roll back to original # */ + break; + } + + /* Success for this digit! */ + if (--ndigs <= 0) { /* Was it last digit? */ + op10m_setz(d.w[1]); /* Clear low AC (high already clear) */ + break; /* And leave loop! */ + } + + CLOCKPOLL(); /* More digits, keep clock going */ + if (INSBRKTEST()) { /* Watch for PI interrupt */ + res = RES_PI; + break; + } + } + + + ac_dset(ac, d); /* Store back binary number */ + ac_setlrh(ac_off(ac,3), flags | (ndigs>>H10BITS), ndigs & H10MASK); + xbpupdate(&s2); /* Store BP back in AC+4,5 */ + switch (res) { + case RES_PF: pag_fail(); + case RES_PI: apr_int(); + default: break; + } + + return PCINC_2; +} + + + +/* Count # decimal digits in double-word binary number, which is guaranteed +** to be positive with sign bits clear in both words. +*/ + +static int +cvtb_digcnt(dw10_t *ad) +{ + register int i; + register dw10_t d; + + d = *ad; + for (i = 1; i < DPOW_MAX; ++i) + if (op10m_udcmpl(d, dpow10[i])) + break; + + /* i has the power of 10 that this number fits under, thus the # of digits + ** needed to represent it is likewise i. i will never be 0, even when + ** the number is zero, because count starts at 1. + */ + return i; +} + +/* CVTDBO - Convert Decimal to Binary Offset (EXTEND [010 ]) + CVTDBT - Convert Decimal to Binary Translated (EXTEND [011 ]) +*/ + +#define CVTF_S H10SIGN /* User sets to indicate pre-existing # */ + +xinsdef(ix_cvtdb) /* Do both CVTDBO and CVTDBT */ +{ + register int32 len1; /* Note signed */ + register int transf; /* TRUE if CVTDBT, else CVTDBO */ + register vaddr_t va; + h10_t flags; + struct cleanbp s1; + w10_t wbyte, woff; + dw10_t d; + unsigned int dig; + enum xires res; + + AC_32GET(len1, ac, 0, 0077000); /* Get src len (may MUUO-fail) */ + flags = ac_getlh(ac) & CVTF_ALL; /* Find flags */ + XBPGET(&s1, ac, 1, BPSRC); /* Set up source BP */ + + /* Check opcode to see if doing translate. Must mask IX_ since enum + ** has special high bit(s). + */ + transf = (xop == (IX_CVTDBT & 0777)); + if (!transf) { /* If not, set up offset */ + if (va_insect(e1) & H10SIGN) /* See if high halfwd bit set */ + LRHSET(woff, H10MASK, va_insect(e1)); /* Yep, sign-extend it */ + else LRHSET(woff, 0, va_insect(e1)); /* Nope */ + } + + if (flags & CVTF_S) /* Number already there? */ + ac_dget(ac_off(ac,3), d); /* Get binary double */ + else { + op10m_dsetz(d); /* Nope, clear it */ + if (!transf) /* If doing offset (CVTDBO), ensure */ + flags |= CVTF_S; /* that S is now set. */ + } + + /* Now loop over source string. Note default result is RES_WON. */ + res = RES_WON; + if (len1 > 0) + for (;;) { /* Loop check is near end */ + + if (xildb(&s1, &wbyte)) { + res = s1.err; + break; + } + --len1; /* Byte gobbled, so bump count */ + if (!transf) { /* Do offset? */ + op10m_add(wbyte, woff); /* Add in offset */ + if (LHGET(wbyte)) { + res = RES_TRUNC; + break; /* Abort, digit is outside range */ + } + dig = RHGET(wbyte); + + } else { /* Do translation */ + register vmptr_t vp; + register h10_t ent; + ent = RHGET(wbyte); + va = e1; + va_add(va, (ent>>1)); /* Get E1+(ent>>1) */ + if (!(vp = vm_xrwmap(va, VMF_READ|VMF_NOTRAP))) { + /* Page-fail trying to fetch translation table entry */ + xdecbp(&s1); /* Back up source bp */ + ++len1; + res = RES_PF; + break; + } + ent = (ent & 01) ? vm_pgetrh(vp) : vm_pgetlh(vp); + switch ((ent >> 15) & 07) { + case 0: break; + case 1: res = RES_TRUNC; break; + case 2: flags &= ~CVTF_M; break; + case 3: flags |= CVTF_M; break; + case 4: flags |= CVTF_S|CVTF_N; break; + case 5: flags |= CVTF_N; res = RES_TRUNC; break; + case 6: flags = CVTF_S|CVTF_N; break; + case 7: flags = CVTF_S|CVTF_N|CVTF_M; break; + } + if (res != RES_WON) /* If wants to stop now, */ + break; /* leave the loop! */ + dig = ent & 017; /* Isolate digit in case S is set */ + } + + /* dig now contains the value 0-9. Add into number so far. */ + if (!transf || (flags & CVTF_S)) { /* Put digit in? */ + dw10_t dtmp; + + /* Verify that it's a 0-9 value! */ + if (dig > 9) { + res = RES_TRUNC; + break; /* Abort, digit is outside range */ + } + + /* Multiply existing number by 10 and then add digit. + ** We do the multiply "by hand" here to avoid extremely slow + ** full-blown DMUL, by using fact multiplier is always 10. + ** This amounts to (x * 8) + (x * 2) which can be done with + ** two shifts and two adds. + */ + dtmp = d; + op10m_ashcleft(dtmp, 1); /* Multiply by 2 */ + op10m_ashcleft(d, 3); /* Multiply by 8 */ + + /* Add together to produce multiply by 10. Code depends on + ** fact that above macros always clear sign bit of low word. + */ + op10m_add(d.w[0], dtmp.w[0]); /* Add high words */ + op10m_add(d.w[1], dtmp.w[1]); /* Add low words */ + if (op10m_signtst(d.w[1])) { /* If low sign now set, */ + op10m_inc(d.w[0]); /* carry to high word */ + op10m_signclr(d.w[1]); /* and clear low sign */ + } + /* Now finally add in the digit! */ + op10m_addi(d.w[1], dig); /* Add into low word */ + if (op10m_signtst(d.w[1])) { /* If low sign now set, */ + op10m_inc(d.w[0]); /* carry to high word */ + op10m_signclr(d.w[1]); /* and clear low sign */ + } + } + + /* This byte done; if any more, do usual clock/PI check */ + if (len1 <= 0) { + res = RES_WON; /* Source all gone, win! */ + break; + } + CLOCKPOLL(); /* More left, keep clock going */ + if (INSBRKTEST()) { /* Watch for PI interrupt */ + res = RES_PI; + break; + } + } + + /* Left loop, see if it succeeded or if we aborted */ + if (res == RES_WON && (flags & CVTF_M)) /* If won, must negate? */ + op10m_dmovn(d); /* Yup, do so */ + + /* Now propagate high sign to low word. This has to be done in a + ** general way since if we started with a pre-loaded value + ** the sign bits are unpredictable. + */ + if (op10m_signtst(d.w[0])) + op10m_signset(d.w[1]); /* Set low sign */ + else + op10m_signclr(d.w[1]); /* Clear low sign */ + + ac_setlrh(ac, flags | (len1>>H10BITS), len1 & H10MASK); + xbpupdate(&s1); /* Store byte ptr in AC+1,2 */ + ac_dset(ac_off(ac,3), d); /* Store back binary number */ + + switch (res) { + case RES_PF: pag_fail(); /* Never returns */ + case RES_PI: apr_int(); /* Never returns */ + case RES_WON: return PCINC_2; /* Skips if ate source w/o probs */ + default: + case RES_TRUNC: return PCINC_1; + } +} + +/* EDIT - Edit String (EXTEND [004 ]) +** +** Extended KL note: +** The pattern string and mark addresses must be handled differently +** depending on whether running extended or not. Test for this is PC +** section; if 0, both mark & pattern are treated as 18-bit addrs, else +** as 30-bit addrs. +** If using 18-bit addrs, having any high bits set <6:17> is +** undefined, but for the KLH10 I'll have them do a MUUO trap. +** OOPS. Judging from the diagnostics, it appears that this is not +** what the machine does. Instead, simply ignore any high bits not +** needed for the address (either 18-bit or 30-bit). This includes +** the high 6 bits of the mark pointer, even though the PRM says they +** are MBZ. +*/ +#define EC_MESSAG 0100 +#define EC_SKPM 0500 +#define EC_SKPN 0600 +#define EC_SKPA 0700 +#define EC0_STOP 000 +#define EC0_SELECT 001 +#define EC0_SIGST 002 +#define EC0_FLDSEP 003 +#define EC0_EXCHMD 004 +#define EC0_NOP 005 + +xinsdef(ix_edit) +{ + register uint32 pp; /* Pattern pointer */ + register vaddr_t ppva; /* Pattern pointer virtual addr */ + register unsigned int ppbn; /* Byte # within pattern word */ + register vaddr_t mark; /* Mark pointer */ + register vmptr_t vp; + register vaddr_t va; /* Random temporary vaddr_t */ + register int inc; + h10_t flags, ent; + struct cleanbp s1, s2; + w10_t pword, wbyte; +#if KLH10_EXTADR + vaddr_t mark2; /* Mark pointer plus 1, for TWGBPs */ +#endif + enum xires res; + + AC_32GET(pp, ac, 0, 0040000); /* Get pattern addr (B3=0) */ + pp &= MASK30; /* 30-bit mask */ +#if KLH10_EXTADR + if (PC_ISEXT) + va_gmake30(ppva, pp); /* Get vaddr_t from 30-bit value */ + else +#endif + { +#if 0 /* This turns out not to be checked on the real machine */ + if (pp & (H10MASK<> 12) & 03; /* Find byte # in pattern word */ + + { + register acptr_t acp; + + acp = ac_map(ac_off(ac,3)); /* Get mark pointer and check it */ +#if 0 /* Not checked on real machine */ + if (op10m_tlnn(*acp, ILLEG_LHVABITS)) + return i_muuo(I_EXTEND, ac, e0); +#endif +#if KLH10_EXTADR + if (PC_ISEXT) { + va_gfrword(mark, *acp); + mark2 = mark; + va_ginc(mark2); + } else +#endif + { +#if 0 /* Not checked on real machine */ + if (LHGET(*acp)) /* Not extended, high bits not OK */ + return i_muuo(I_EXTEND, ac, e0); +#endif + va_lmake(mark, 0, RHGET(*acp)); +#if KLH10_EXTADR + mark2 = mark; + va_linc(mark2); +#endif + } + } + + XBPGET(&s1, ac, 1, BPSRC); /* Set up byte ptrs, may page-fail */ + XBPGET(&s2, ac, 4, BPDST); + + /* Start loop; leaves it only via goto! + ** Note we check for PI only when fetching a new command word + ** rather than after each command byte. This will delay any PI + ** by up to 3 edit commands but that should be okay. + */ + pword = vm_read(ppva); /* Set up pattern word, may page-fail */ + inc = 0; + for (;;) { + /* First increment pattern pointer and fetch command */ + ppbn += inc; + if (ppbn > 3) { + pp += (ppbn >> 2); /* Increment word ptr */ + va_add(ppva, (ppbn>>2)); + ppbn &= 03; /* Reset byte # */ + + /* Now check for events before doing next */ + CLOCKPOLL(); /* Keep clock going */ + if (INSBRKTEST()) { /* Watch for PI interrupt */ + res = RES_PI; + goto cleanup; + } + /* Fetch next word of 4 commands */ + if (!(vp = vm_xrwmap(ppva, VMF_READ|VMF_NOTRAP))) { + goto pf_presrc; /* Take pagefail exit */ + } + pword = vm_pget(vp); /* Get new pattern word */ + } + inc = 1; /* Reset to normal increment */ + switch (ppbn & 03) { + case 0: ent = LHGET(pword) >> 9; break; + case 1: ent = LHGET(pword) & 0777; break; + case 2: ent = RHGET(pword) >> 9; break; + case 3: ent = RHGET(pword) & 0777; break; + } + + /* Now decode pattern command */ + switch (ent & 0700) { + case EC_MESSAG: /* Insert fill-type char */ + va = e0; + if (flags & CVTF_S) + va_add(va, (ent&077)+1); /* Get E0+(ent&077)+1 */ + else + va_add(va, 1); /* Get E0+1 */ + vp = vm_xrwmap(va, VMF_READ|VMF_NOTRAP); + if (!vp) + goto pf_presrc; /* Take pagefail exit */ + wbyte = vm_pget(vp); + if ((flags & CVTF_S) || op10m_skipn(wbyte)) { + if (xidpb(&s2, &wbyte)) + goto pf2_presrc; /* No backup, take pagefail error */ + } + continue; + case EC_SKPM: /* Skip if M set */ + if (flags & CVTF_M) + inc = (ent & 077) + 2; /* Include PP+1 too */ + continue; + case EC_SKPN: /* Skip if N set */ + if (!(flags & CVTF_N)) + continue; /* Nope */ + /* N set, drop through to unconditional skip */ + case EC_SKPA: /* Skip next n+1 commands */ + inc = (ent & 077) + 2; /* Include PP+1 too */ + continue; + + /* Not a recognized class, check entire byte as command */ + default: switch (ent) { + case EC0_SELECT: /* Select Next source byte */ + break; /* Drop out - complex subcommand */ + case EC0_SIGST: + if (flags & CVTF_S) /* If S already set, */ + continue; /* treat as no-op */ + /* Do things in this sequence so if we page-fail nothing + ** has to be backed out of. + */ + va = e0; + va_add(va, 2); /* Get E0+2 */ + if (!(vp = vm_xrwmap(va, VMF_READ|VMF_NOTRAP))) + goto pf_presrc; + wbyte = vm_pget(vp); /* Get "float char" */ + if (!(vp = vm_xrwmap(mark, VMF_WRITE|VMF_NOTRAP))) + goto pf_presrc; + + xbpupdate(&s2); /* Store dest BP back in AC+4 */ + vm_pset(vp, ac_get(ac_off(ac,4))); /* Also store at mark */ +#if KLH10_EXTADR + /* May need to set 2nd word of mark, if dest BP is 2-word */ + if (PC_ISEXT) { + if (vm_issafedouble(mark)) + vp++; + else if (!(vp = vm_xrwmap(mark2, VMF_WRITE|VMF_NOTRAP))) + goto pf_presrc; + vm_pset(vp, ac_get(ac_off(ac,5))); /* Store 2nd wd */ + } +#endif + if (op10m_skipn(wbyte)) { /* If float char non-zero, */ + if (xidpb(&s2, &wbyte)) /* try storing it */ + goto pf2_presrc; /* Fail, no backup */ + } + flags |= CVTF_S; /* OK to set flag now! */ + continue; + case EC0_FLDSEP: /* Separate fields */ + flags &= ~(CVTF_S|CVTF_M|CVTF_N); + continue; + case EC0_EXCHMD: /* Exchange Mark and Dest BP */ + /* Find length of dest BP, and assume mark is same + ** For now (non-extended), always assume 1 word. + ** This code can be improved! + */ + xbpupdate(&s2); /* Store dest BP back in AC+4 */ +#if KLH10_EXTADR + /* Gross hair to handle 2-word BPs */ + if (PC_ISEXT) { + vmptr_t vp2; + if (!(vp = vm_xrwmap(mark, VMF_WRITE|VMF_NOTRAP))) + goto pf_presrc; + if (vm_issafedouble(mark)) + vp2 = vp+1; + else if (!(vp2 = vm_xrwmap(mark2, VMF_WRITE|VMF_NOTRAP))) + goto pf_presrc; + wbyte = ac_get(ac_off(ac,5)); /* Save 2nd dst wd */ + ac_set(ac_off(ac,5), vm_pget(vp2)); /* Set 2nd dst wd */ + vm_pset(vp2, wbyte); /* Set 2nd mark wd */ + } else /* Do normal 1-word case */ +#endif + { + if (!(vp = vm_xrwmap(mark, VMF_WRITE|VMF_NOTRAP))) + goto pf_presrc; + } + + wbyte = ac_get(ac_off(ac,4)); /* Save 1st dst wd */ + ac_set(ac_off(ac,4), vm_pget(vp)); /* Set 1st dst wd */ + vm_pset(vp, wbyte); /* Set 1st mark wd */ + + /* Set up bp for internal use, fail if error result + ** (most likely MUUO for bogus OWGBP, if extended) + */ + if (res = xbpinit(&s2, ac_off(ac,4), BPDST)) { + /* Note: Not sure if this is correct behavior, since at + ** this point the AC has already been clobbered, and the + ** rest of the world is probably not in a very good + ** state either. Perhaps try to restore from saved BP? + */ + goto cleanup; + } + continue; + + default: + /* NOTE! Unrecognized pattern bytes are simply treated + ** as NOPs. + ** Fall through to handle like NOP. + */ + case EC0_NOP: + continue; + case EC0_STOP: /* Stop Edit */ + res = RES_WON; + if (++ppbn > 3) /* Increment PP one last time */ + ppbn = 0, ++pp, va_inc(ppva); + goto cleanup; + } + } + + /* Drop through to handle SELECT command + ** Because the source string BP is incremented here, any pagefail + ** after a successful xildb must jump to pf_postsrc instead + ** of pf_presrc. + */ + if (xildb(&s1, &wbyte)) { /* Get source byte */ + res = s1.err; /* oops */ + goto cleanup; /* no backup, not incremented yet */ + } + + /* Carry out translation-type subcommand */ + ent = RHGET(wbyte); + va = e1; + va_add(va, (ent>>1)); /* Get E1 + (ent>>1) */ + if (!(vp = vm_xrwmap(va, VMF_READ|VMF_NOTRAP))) { + goto pf_postsrc; /* Page fail, back up source BP */ + } + + /* Do translation (similar to CVTDBT) */ + ent = (ent & 01) ? vm_pgetrh(vp) : vm_pgetlh(vp); + switch ((ent >> 15) & 07) { + case 2: + flags &= ~CVTF_M; + if (1) ; /* Hack to skip over next stmt */ + else { + case 3: + flags |= CVTF_M; + } + case 0: + if (flags & CVTF_S) + LRHSET(wbyte, 0, ent & 07777); /* Set up translated byte */ + else { /* Get fill byte, check it */ + va = e0; + va_inc(va); /* Get E0+1 */ + if (!(vp = vm_xrwmap(va, VMF_READ|VMF_NOTRAP))) + goto pf_postsrc; + wbyte = vm_pget(vp); + if (!op10m_skipn(wbyte)) /* If fill byte zero, */ + continue; /* do nothing */ + } + if (xidpb(&s2, &wbyte)) /* Try storing it */ + goto pf2_postsrc; /* Ugh, back out and fail */ + continue; + + case 5: + flags |= CVTF_N; + /* Drop thru to terminate edit */ + case 1: + res = RES_TRUNC; + if (++ppbn > 3) /* Increment PP one last time */ + ppbn = 0, ++pp, va_inc(ppva); + goto cleanup; + + case 6: + flags &= ~CVTF_M; + if (1) ; /* Hack to skip over next stmt */ + else { + case 7: + flags |= CVTF_M; + } + case 4: + flags |= CVTF_N; + if (!(flags & CVTF_S)) { + /* These are exactly the same actions as SIGST!! */ + /* Do things in this sequence so if we page-fail nothing + ** (other than source BP) has to be backed out of. + */ + va = e0; + va_add(va, 2); /* Get E0+2 */ + if (!(vp = vm_xrwmap(va, VMF_READ|VMF_NOTRAP))) + goto pf_postsrc; + wbyte = vm_pget(vp); /* Get "float char" */ + if (!(vp = vm_xrwmap(mark, VMF_WRITE|VMF_NOTRAP))) + goto pf_postsrc; + + xbpupdate(&s2); /* Store dest BP back in AC+4 */ + vm_pset(vp, ac_get(ac_off(ac,4))); /* Also store at mark */ +#if KLH10_EXTADR + /* May need to set 2nd word of mark, if dest BP is 2-word */ + if (PC_ISEXT) { + if (vm_issafedouble(mark)) + vp++; + else if (!(vp = vm_xrwmap(mark2, VMF_WRITE|VMF_NOTRAP))) + goto pf_postsrc; + vm_pset(vp, ac_get(ac_off(ac,5))); /* Store 2nd wd */ + } +#endif + if (op10m_skipn(wbyte)) { /* If float char non-zero, */ + if (xidpb(&s2, &wbyte)) /* try storing it */ + goto pf2_postsrc; /* No backup needed. */ + } + flags |= CVTF_S; /* OK to set flag now! */ + } + LRHSET(wbyte, 0, ent & 07777); /* Set up translated byte */ + if (xidpb(&s2, &wbyte)) /* Try storing it */ + goto pf2_postsrc; + continue; + } + /* Should never come here */ + } + + /* Common return points for pagefails. Control never drops or breaks + ** out of main loop except with gotos. + */ +pf2_postsrc: + xdecbp(&s1); /* Back up source bp */ +pf2_presrc: + res = s2.err; /* Get specific error result from S2 ref */ + goto cleanup; + +pf_postsrc: + xdecbp(&s1); /* Back up source bp */ +pf_presrc: /* No backup needed */ + res = RES_PF; + +cleanup: + /* Done, update ACs and return appropriate PC increment + */ + ac_setlrh(ac, flags | ((uint18)ppbn << 12) | (pp>>H10BITS), pp & H10MASK); + xbpupdate(&s1); /* Set AC+1, AC+2 */ + /* AC+3: Mark address is never changed, so needn't store it back */ + xbpupdate(&s2); /* Set AC+4, AC+5 */ + + switch (res) { + case RES_MUUO: return i_muuo(I_EXTEND, ac, e0); + case RES_PF: pag_fail(); /* Never returns */ + case RES_PI: apr_int(); /* Never returns */ + case RES_WON: return PCINC_2; /* Skips if source exhausted */ + default: + case RES_TRUNC: return PCINC_1; + } +} + +#endif /* T10 || T20 */ diff --git a/src/infix.c b/src/infix.c new file mode 100644 index 0000000..aa66131 --- /dev/null +++ b/src/infix.c @@ -0,0 +1,153 @@ +/* INFIX.C - Fixed-Point arithmetic instruction routines +*/ +/* $Id: infix.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: infix.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#include "klh10.h" +#include "kn10def.h" +#include "kn10ops.h" + +#ifdef RCSID + RCSID(infix_c,"$Id: infix.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* See CODING.TXT for guidelines to coding instruction routines. */ + + +/* Fixed-point arithmetic, single-word operands & results */ + +#define addinsdef(name, nameI, nameM, nameB) \ + insdef(name) \ + { ac_set(ac, fixop(ac_get(ac), vm_read(e))); \ + return PCINC_1; \ + } \ + insdef(nameI) \ + { register w10_t w; \ + immop(); \ + return PCINC_1; \ + } \ + insdef(nameM) \ + { register vmptr_t p = vm_modmap(e); \ + vm_pset(p, fixop(ac_get(ac), vm_pget(p))); \ + return PCINC_1; \ + } \ + insdef(nameB) \ + { register vmptr_t p = vm_modmap(e); \ + ac_set(ac, fixop(ac_get(ac), vm_pget(p))); \ + vm_pset(p, ac_get(ac)); \ + return PCINC_1; \ + } + +#define fixop(a,b) op10add((a),(b)) +#define immop() w = ac_get(ac); op10mf_addi(w,va_insect(e)); ac_set(ac,w) + addinsdef(i_add, i_addi, i_addm, i_addb) +#undef immop +#undef fixop + +#define fixop(a,b) op10sub((a),(b)) +#define immop() w = ac_get(ac); op10mf_subi(w,va_insect(e)); ac_set(ac,w) + addinsdef(i_sub, i_subi, i_subm, i_subb) +#undef immop +#undef fixop + +#define fixop(a,b) op10imul((a),(b)) +#define immop() LRHSET(w, 0, va_insect(e)); ac_set(ac, op10imul(ac_get(ac), w)) + addinsdef(i_imul, i_imuli, i_imulm, i_imulb) +#undef immop +#undef fixop +#undef addinsdef + + +/* Handle single-word instructions that produce double results */ + +#define fixlongdef(name, nameI, nameM, nameB) \ + insdef(name) \ + { dw10_t d; \ + fixop(d, ac, vm_read(e)); \ + ac_dset(ac, d); \ + return PCINC_1; \ + } \ + insdef(nameI) \ + { dw10_t d; \ + register w10_t w; LRHSET(w, 0, va_insect(e)); \ + fixop(d, ac, w); \ + ac_dset(ac, d); \ + return PCINC_1; \ + } \ + insdef(nameM) \ + { register vmptr_t p = vm_modmap(e); \ + dw10_t d; \ + fixop(d, ac, vm_pget(p)); \ + vm_pset(p, HIGET(d)); \ + return PCINC_1; \ + } \ + insdef(nameB) \ + { register vmptr_t p = vm_modmap(e); \ + dw10_t d; \ + fixop(d, ac, vm_pget(p)); \ + vm_pset(p, HIGET(d)); \ + ac_dset(ac, d); \ + return PCINC_1; \ + } + +#define fixop(d,a,b) (d = op10mul(ac_get(a),(b))) + fixlongdef(i_mul, i_muli, i_mulm, i_mulb) +#undef fixop + +#define fixop(d,a,b) if (!op10xidiv(&d, ac_get(a),(b))) return PCINC_1 + fixlongdef(i_idiv, i_idivi, i_idivm, i_idivb) +#undef fixop + +/* Note special hackery to furnish double-word arg to OP10XDIV */ +#define fixop(d,a,b) ac_dget(a,d); if(!op10xdiv(&d,(b))) return PCINC_1 + fixlongdef(i_div, i_divi, i_divm, i_divb) +#undef fixop + +#undef fixlongdef + +/* Double-precision fixed-point arithmetic */ + +#define doublefix(name, fixop) \ + insdef(name) \ + { register dw10_t da, de; \ + ac_dget(ac, da); \ + vm_dread(e, de); \ + hackstmt(fixop) \ + return PCINC_1; \ + } +#define hackstmt(op) da = op(da, de); ac_dset(ac, da); + doublefix(i_dadd, op10dadd) + doublefix(i_dsub, op10dsub) +#undef hackstmt + +#define hackstmt(op) { qw10_t q; q = op(da, de); \ + ac_dset(ac, q.d[0]); \ + ac_dset(ac_off(ac,2), q.d[1]); } + doublefix(i_dmul, op10dmul) +#undef hackstmt + +#define hackstmt(op) { qw10_t q; q.d[0] = da; ac_dget(ac_off(ac,2),q.d[1]);\ + q = op(q, de); \ + ac_dset(ac, q.d[0]); \ + ac_dset(ac_off(ac,2), q.d[1]); } + doublefix(i_ddiv, op10ddiv) +#undef hackstmt diff --git a/src/inflt.c b/src/inflt.c new file mode 100644 index 0000000..4646663 --- /dev/null +++ b/src/inflt.c @@ -0,0 +1,327 @@ +/* INFLT.C - Floating-Point arithmetic instruction routines +*/ +/* $Id: inflt.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: inflt.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#include "klh10.h" +#include "kn10def.h" +#include "kn10ops.h" + +#ifdef RCSID + RCSID(inflt_c,"$Id: inflt.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* See CODING.TXT for guidelines to coding instruction routines. */ + + +/* Single-precision Floating point arithmetic */ + +#define singleflt(fltop, name, nameM, nameB) \ + insdef(name) \ + { ac_set(ac, fltop(ac_get(ac), vm_read(e))); \ + return PCINC_1; \ + } \ + insdef(nameM) \ + { register vmptr_t p = vm_modmap(e); \ + vm_pset(p, fltop(ac_get(ac), vm_pget(p))); \ + return PCINC_1; \ + } \ + insdef(nameB) \ + { register vmptr_t p = vm_modmap(e); \ + ac_set(ac, fltop(ac_get(ac), vm_pget(p))); \ + vm_pset(p, ac_get(ac)); \ + return PCINC_1; \ + } + +#define singlefimm(fltop, nameI) \ + insdef(nameI) \ + { register w10_t w; \ + LRHSET(w, va_insect(e), 0); \ + ac_set(ac, fltop(ac_get(ac), w)); \ + return PCINC_1; \ + } + +#define singleflong(longop, nameL) \ + insdef(nameL) \ + { register dw10_t d; \ + d = longop(ac_get(ac),vm_read(e)); \ + ac_dset(ac,d); \ + return PCINC_1; \ + } + + + + singleflt(op10fadr, i_fadr, i_fadrm, i_fadrb) + singleflt(op10fsbr, i_fsbr, i_fsbrm, i_fsbrb) + singleflt(op10fmpr, i_fmpr, i_fmprm, i_fmprb) + singlefimm(op10fadr, i_fadri) + singlefimm(op10fsbr, i_fsbri) + singlefimm(op10fmpr, i_fmpri) + + singleflt(op10fad, i_fad, i_fadm, i_fadb) + singleflt(op10fsb, i_fsb, i_fsbm, i_fsbb) + singleflt(op10fmp, i_fmp, i_fmpm, i_fmpb) + singleflong(op10fadl, i_fadl) /* "Immediate" mode for FAD is FADL */ + singleflong(op10fsbl, i_fsbl) /* "Immediate" mode for FSB is FSBL */ + singleflong(op10fmpl, i_fmpl) /* "Immediate" mode for FMP is FMPL */ + +#undef singleflt +#undef singlefimm +#undef singleflong + + +/* Variation for divide-type ops since divide may fail */ + +#define singlefdiv(name, nameM, nameB) \ + insdef(name) \ + { w10_t a; \ + a = ac_get(ac); \ + if (divop(&a, vm_read(e))) \ + ac_set(ac, a); \ + return PCINC_1; \ + } \ + insdef(nameM) \ + { register vmptr_t p = vm_modmap(e); \ + w10_t a; \ + a = ac_get(ac); \ + if (divop(&a, vm_pget(p))) \ + vm_pset(p, a); \ + return PCINC_1; \ + } \ + insdef(nameB) \ + { register vmptr_t p = vm_modmap(e); \ + w10_t a; \ + a = ac_get(ac); \ + if (divop(&a, vm_pget(p))) { \ + vm_pset(p, a); \ + ac_set(ac, a); \ + } \ + return PCINC_1; \ + } + +#define divop(a,b) op10xfdv(a,b,1) /* Use rounding */ + singlefdiv(i_fdvr, i_fdvrm, i_fdvrb) +#undef divop + +#define divop(a,b) op10xfdv(a,b,0) /* Use no rounding */ + singlefdiv(i_fdv, i_fdvm, i_fdvb) +#undef divop + +#undef singlefdiv + + +insdef(i_fdvri) /* Special-case FDVRI */ +{ + register w10_t w; + w10_t a; + a = ac_get(ac); + LRHSET(w, va_insect(e), 0); + if (op10xfdv(&a, w, 1)) + ac_set(ac, a); + return PCINC_1; +} + +insdef(i_fdvl) /* Special-case FDVL (immediate mode for FDV) */ +{ + register dw10_t d; + ac_dget(ac, d); + d = op10fdvl(d, vm_read(e)); + ac_dset(ac, d); + return PCINC_1; +} + +/* Double-precision Floating-point */ + +#define doubleflt(name, fltop) \ + insdef(name) \ + { register dw10_t da, de; \ + ac_dget(ac, da); \ + vm_dread(e, de); \ + da = fltop(da, de); \ + ac_dset(ac, da); \ + return PCINC_1; \ + } +doubleflt(i_dfad, op10dfad) +doubleflt(i_dfsb, op10dfsb) +doubleflt(i_dfmp, op10dfmp) +doubleflt(i_dfdv, op10dfdv) + +/* G-Format floating-point (also double-word ops) */ + +#if KLH10_CPU_KL +doubleflt(i_gfad, op10gfad) +doubleflt(i_gfsb, op10gfsb) +doubleflt(i_gfmp, op10gfmp) +doubleflt(i_gfdv, op10gfdv) +#endif /* KL */ + +#undef doubleflt + +/* Format Conversion (and UFA, DFN) */ + +insdef(i_fsc) +{ + ac_set(ac, op10fsc(ac_get(ac), (h10_t)va_insect(e))); + return PCINC_1; +} + +insdef(i_fix) +{ + w10_t w; + w = vm_read(e); + if (op10xfix(&w, 0)) /* Fix, no round, TRUE if succeeded */ + ac_set(ac, w); + return PCINC_1; +} + +insdef(i_fixr) +{ + w10_t w; + w = vm_read(e); + if (op10xfix(&w, 1)) /* Fix, with round, TRUE if succeeded */ + ac_set(ac, w); + return PCINC_1; +} + +insdef(i_fltr) +{ + ac_set(ac, op10fltr(vm_read(e))); + return PCINC_1; +} + + +/* Obsolete KA10 instructions - DFN, UFA +** Note FADL/FSBL/FMPL/FDVL are also obsolete KA ops. Those were defined +** earlier in the single-precision section. +*/ + +insdef(i_dfn) +{ + dw10_t d; + register vmptr_t p = vm_modmap(e); /* Set up to mung c(E) */ + d.w[0] = ac_get(ac); /* Get hi word of double */ + d.w[1] = vm_pget(p); /* Get lo word */ + d = op10dfn(d); /* Do it! */ + ac_set(ac, d.w[0]); /* Store results away */ + vm_pset(p, d.w[1]); + return PCINC_1; +} + +insdef(i_ufa) +{ + ac_set(ac_off(ac,1), op10ufa(ac_get(ac), vm_read(e))); + return PCINC_1; +} + +/* Double-Precision (G-Format) conversions */ + +#if KLH10_CPU_KL + +xinsdef(ix_gfsc) /* EXTEND [031 ] */ +{ + register dw10_t d; + + ac_dget(ac, d); + d = op10gfsc(d, (h10_t)va_insect(e1)); + ac_dset(ac, d); + return PCINC_1; +} + +xinsdef(ix_gsngl) /* EXTEND [021 ] */ +{ + dw10_t d; + + vm_dread(e1, d); + if (op10xgsngl(&d)) /* G->F, with round, TRUE if succeeded */ + ac_set(ac, d.w[0]); + return PCINC_1; +} + +xinsdef(ix_gdble) /* EXTEND [022 ] */ +{ + register dw10_t d; + + d = op10gdble(vm_read(e1)); /* F->G */ + ac_dset(ac, d); + return PCINC_1; +} + + +xinsdef(ix_dgfltr) /* EXTEND [027 ] */ +{ + register dw10_t d; + + vm_dread(e1, d); + d = op10dgfltr(d); /* Dfix -> G with round */ + ac_dset(ac, d); + return PCINC_1; +} + +xinsdef(ix_gfltr) /* EXTEND [030 ] */ +{ + register dw10_t d; + + d = op10gfltr(vm_read(e1)); /* fix -> G */ + ac_dset(ac, d); + return PCINC_1; +} + + +# if 0 +/* These instructions are currently simulated in T10+T20 monitors. +** Later can provide the real thing in KN10OPS and activate this code. +*/ +xinsdef(ix_gdfix) /* EXTEND [023 ] */ +{ + dw10_t d; + vm_dread(e1, d); + if (op10xgdfix(&d, 0)) /* G -> Dfix, no round, TRUE if succeeded */ + ac_dset(ac, d); + return PCINC_1; +} +xinsdef(ix_gfix) /* EXTEND [024 ] */ +{ + dw10_t d; + vm_dread(e1, d); + if (op10xgfix(&d, 0)) /* G -> fix, no round, TRUE if succeeded */ + ac_set(ac, d.w[0]); + return PCINC_1; +} +xinsdef(ix_gdfixr) /* EXTEND [025 ] */ +{ + dw10_t d; + vm_dread(e1, d); + if (op10xgdfix(&d, 1)) /* G -> Dfix, with round, TRUE if succeeded */ + ac_dset(ac, d); + return PCINC_1; +} +xinsdef(ix_gfixr) /* EXTEND [026 ] */ +{ + dw10_t d; + vm_dread(e1, d); + if (op10xgfix(&d, 1)) /* G -> fix, with round, TRUE if succeeded */ + ac_set(ac, d.w[0]); + return PCINC_1; +} +# endif /* 0 */ + +#endif /* KLH10_CPU_KL */ diff --git a/src/inhalf.c b/src/inhalf.c new file mode 100644 index 0000000..bb5d539 --- /dev/null +++ b/src/inhalf.c @@ -0,0 +1,164 @@ +/* INHALF.C - Halfword instruction routines +*/ +/* $Id: inhalf.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: inhalf.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#include "klh10.h" +#include "kn10def.h" /* Machine defs */ +#include "kn10ops.h" /* PDP-10 ops */ + +#ifdef RCSID + RCSID(inhalf_c,"$Id: inhalf.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* See CODING.TXT for guidelines to coding instruction routines. */ + +/* Halfword data hackery */ + +#define halfinsdef(name, nameI, nameM, nameS) \ + insdef(name) \ + { register w10_t w; \ + halfop(w, vm_read(e), ac_get(ac)); \ + ac_set(ac, w); \ + return PCINC_1; \ + } \ + insdef(nameI) \ + { register w10_t v, w; /* Need 2 to avoid self-clobber */\ + LRHSET(v, 0, va_insect(e)); \ + halfop(w, v, ac_get(ac)); \ + ac_set(ac, w); \ + return PCINC_1; \ + } \ + insdef(nameM) \ + { register w10_t w; \ + register vmptr_t p = vm_modmap(e); \ + halfop(w, ac_get(ac), vm_pget(p)); \ + vm_pset(p, w); \ + return PCINC_1; \ + } \ + insdef(nameS) \ + { register w10_t w; \ + register vmptr_t p = vm_modmap(e); \ + halfop(w, vm_pget(p), vm_pget(p)); \ + vm_pset(p, w); \ + if (ac) ac_set(ac, w); \ + return PCINC_1; \ + } + + /* -------------------------- */ + +#define halfop(w,sh,dh) (LRHSET(w, LHGET(sh), RHGET(dh))) +halfinsdef(i_hll, i_hlli, i_hllm, i_hlls) /* HLL */ +#undef halfop + +#define halfop(w,sh,dh) (LRHSET(w, LHGET(sh), 0)) +halfinsdef(i_hllz, i_hllzi, i_hllzm, i_hllzs) /* HLLZ */ +#undef halfop + +#define halfop(w,sh,dh) (LRHSET(w, LHGET(sh), H10ONES)) +halfinsdef(i_hllo, i_hlloi, i_hllom, i_hllos) /* HLLO */ +#undef halfop + +#if 1 +# define halfop(w,sh,dh) w = (sh); if (op10m_signtst(w)) \ + op10m_tro(w, H10ONES); else op10m_trz(w, H10ONES) +#else /* Old code - trips warnings with DU 4.0F compiler */ +# define halfop(w,sh,dh) (LHSET(w, LHGET(sh)), \ + RHSET(w,((LHGET(w)&H10SIGN) ? H10ONES : 0))) +#endif +halfinsdef(i_hlle, i_hllei, i_hllem, i_hlles) /* HLLE */ +#undef halfop + + /* -------------------------- */ + +#define halfop(w,sh,dh) (LRHSET(w, LHGET(dh), LHGET(sh))) +halfinsdef(i_hlr, i_hlri, i_hlrm, i_hlrs) /* HLR */ +#undef halfop + +#define halfop(w,sh,dh) (LRHSET(w, 0, LHGET(sh))) +halfinsdef(i_hlrz, i_hlrzi, i_hlrzm, i_hlrzs) /* HLRZ */ +#undef halfop + +#define halfop(w,sh,dh) (LRHSET(w, H10ONES, LHGET(sh))) +halfinsdef(i_hlro, i_hlroi, i_hlrom, i_hlros) /* HLRO */ +#undef halfop + +#if 1 +# define halfop(w,sh,dh) LRHSET(w, 0, LHGET(sh)); \ + if (op10m_trnn(w, H10SIGN)) op10m_tlo(w, H10ONES) +#else /* Old code - trips warnings with DU 4.0F compiler */ +# define halfop(w,sh,dh) (RHSET(w, LHGET(sh)), \ + LHSET(w,((RHGET(w)&H10SIGN) ? H10ONES : 0))) +#endif +halfinsdef(i_hlre, i_hlrei, i_hlrem, i_hlres) /* HLRE */ +#undef halfop + + /* -------------------------- */ + +#define halfop(w,sh,dh) (LRHSET(w, LHGET(dh), RHGET(sh))) +halfinsdef(i_hrr, i_hrri, i_hrrm, i_hrrs) /* HRR */ +#undef halfop + +#define halfop(w,sh,dh) (LRHSET(w, 0, RHGET(sh))) +halfinsdef(i_hrrz, i_hrrzi, i_hrrzm, i_hrrzs) /* HRRZ */ +#undef halfop + +#define halfop(w,sh,dh) (LRHSET(w, H10ONES, RHGET(sh))) +halfinsdef(i_hrro, i_hrroi, i_hrrom, i_hrros) /* HRRO */ +#undef halfop + +#if 1 +# define halfop(w,sh,dh) w = (sh); if (op10m_trnn(w, H10SIGN)) \ + op10m_tlo(w, H10ONES); else op10m_tlz(w, H10ONES) +#else /* Old code - trips warnings with DU 4.0F compiler */ +# define halfop(w,sh,dh) (RHSET(w, RHGET(sh)), \ + LHSET(w,((RHGET(w)&H10SIGN) ? H10ONES : 0))) +#endif +halfinsdef(i_hrre, i_hrrei, i_hrrem, i_hrres) /* HRRE */ +#undef halfop + + /* -------------------------- */ + +#define halfop(w,sh,dh) (LRHSET(w, RHGET(sh), RHGET(dh))) +halfinsdef(i_hrl, i_hrli, i_hrlm, i_hrls) /* HRL */ +#undef halfop + +#define halfop(w,sh,dh) (LRHSET(w, RHGET(sh), 0)) +halfinsdef(i_hrlz, i_hrlzi, i_hrlzm, i_hrlzs) /* HRLZ */ +#undef halfop + +#define halfop(w,sh,dh) (LRHSET(w, RHGET(sh), H10ONES)) +halfinsdef(i_hrlo, i_hrloi, i_hrlom, i_hrlos) /* HRLO */ +#undef halfop + +#if 1 +# define halfop(w,sh,dh) LRHSET(w, RHGET(sh), 0); \ + if (op10m_signtst(w)) op10m_tro(w, H10ONES) +#else /* Old code - trips warnings with DU 4.0F compiler */ +# define halfop(w,sh,dh) (LHSET(w, RHGET(sh)), \ + RHSET(w,((LHGET(w)&H10SIGN) ? H10ONES : 0))) +#endif +halfinsdef(i_hrle, i_hrlei, i_hrlem, i_hrles) /* HRLE */ +#undef halfop + +#undef halfinsdef + diff --git a/src/inio.c b/src/inio.c new file mode 100644 index 0000000..9282e31 --- /dev/null +++ b/src/inio.c @@ -0,0 +1,681 @@ +/* INIO.C - I/O Instructions (Ouch!!) +*/ +/* $Id: inio.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: inio.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#include "klh10.h" +#include "kn10def.h" +#include "kn10ops.h" +#include "kn10dev.h" +#include "dvuba.h" + +#ifdef RCSID + RCSID(inio_c,"$Id: inio.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* See CODING.TXT for guidelines to coding instruction routines. */ + +/* + "IO Instruction" theoretically includes all 07xx opcodes, although +with newer processors (KL and KS) it is possible to have essentially +normal instructions with opcodes in the 07xx range. In the KLH10 the +following terms will hold: + dev-IO instruction - one interpreted as CONI, DATAI, etc. + Functions handling these instructions are given only a + device handle as argument. + IO instruction - above, plus 07xx instructions either interpreted as + applying to "internal" devices, or further dispatched by + by their AC field. + Functions handling these instructions are given only E as + argument. + + But all opcodes of 07xx are subject to the IO instruction +restrictions: + (1) User mode illegal unless UIO set (else does MUUO trap) + (2) On KI & KL, dev-style IO instrs are ok in user mode for devices 740 up. + This corresponds to opcodes of 774-777 inclusive. + (on KA, need UIO; on KS, illegal->MUUO) + +Internal devices: External + KA10: 2 (APR, PI) (CLK, PTR, ...) + KI10: 3 (APR, PI, PAG) (CLK, PTR, ...) + KL10: 6 (APR, PI, PAG, CCA, TIM, MTR) + KS10: no dev-style, all "internal" + +External devices: + 4 basic functions: DATAI, DATAO, CONI, CONO. + For nonexistent external devices, + DATAI and CONI read zero, + DATAO and CONO are no-ops. + +On the KS10, + all "IO instrs" not specifically defined are executed as MUUOs. + However, note CONSZ/CONSO APR/PI are defined even though the PRM + doesn't mention them. + +SPECIAL NOTE for the KL10: + PRM p.3-2 states: "CAUTION" + "All IO instructions in this chapter are for internal devices + (E bus functions). An address given by such an instruction + for storing a result is always interpreted as global in the + section containing the instruction. Hence data or conditions + in cannot be stored in an AC unless the instruction is in + section 0 or 1." + [Does this apply to external devices as well? Assume not.] + [What about the E for DATAO dev,E ?? Assume not affected by this, + since data is going OUT, not in -- normal CPU mem ref fetch, then given + to IO system. I'm imagining a model where the "IO system", not the + uprocessor itself, is being given responsibility for storing input + data, and is crippled in its ability to do so.] + + + Dev-IO op KS10 ITSKS KL10 KI10 KA10 +700 AC + 0 BI APR, APRID == == ? ? + 1 DI APR, -UUO- == DI_APR DI_APR == + 2 BO APR, -UUO- == WRFIL ? ? + 3 DO APR, -UUO- == DO_APR DO_APR DO_APR + 4 CO APR, WRAPR == == == == + 5 CI APR, RDAPR == == == == + 6 SZ APR, == == == == == + 7 SO APR, == == == == == + 10 BI PI, -UUO- == RDERA ? ? + 11 DI PI, -UUO- == == ? ? ; KL: stats option + 12 BO PI, -UUO- == SBDIAG ? ? + 13 DO PI, -UUO- == == DO_PI ? ; KL: stats option + 14 CO PI, WRPI == == == == + 15 CI PI, RDPI == == == == + 16 SZ PI, == == == == == + 17 SO PI, == == == == == +701 AC + 0 BI PAG, -UUO- CLRCSH -UUO- ? + 1 DI PAG, RDUBR == DI_PAG DI_PAG + 2 BO PAG, CLRPT == == ? + 3 DO PAG, WRUBR == DO_PAG DO_PAG + 4 CO PAG, WREBR == CO_PAG CO_PAG + 5 CI PAG, RDEBR == CI_PAG CI_PAG + 6 SZ PAG, -UUO- == SZ PAG, == + 7 SO PAG, -UUO- == SO PAG, == + 10 BI CCA, -UUO- == "SWP" + 11 DI CCA, -UUO- RDPCST SWPIA + 12 BO CCA, -UUO- == SWPVA + 13 DO CCA, -UUO- WRPCST SWPUA + 14 CO CCA, -UUO- == "SWP" + 15 CI CCA, -UUO- == SWPIO + 16 SZ CCA, -UUO- == SWPVO + 17 SO CCA, -UUO- == SWPUO +702 AC + 0 BI TIM, RDSPB SDBR1 RDPERF + 1 DI TIM, RDCSB SDBR2 RDTIME + 2 BO TIM, RDPUR SDBR3 WRPAE + 3 DO TIM, RDCSTM SDBR4 -MUUO-* [* PRM 3-54 fn.39] + 4 CO TIM, RDTIM == CO_TIM + 5 CI TIM, RDINT == CI_TIM + 6 SZ TIM, RDHSB == SZ TIM, + 7 SO TIM, -UUO- SPM SO TIM, + 10 BI MTR, WRSPB LDBR1 RDMACT + 11 DI MTR, WRCSB LDBR2 RDEACT + 12 BO MTR, WRPUR LDBR3 -MUUO-* [* PRM 3-54 fn.39] + 13 DO MTR, WRHSB LDBR4 -MUUO-* [* PRM 3-54 fn.39 corrected] + 14 CO MTR, WRTIM == WRTIME + 15 CI MTR, WRINT == CI_MTR + 16 SZ MTR, WRHSB == SZ MTR, + 17 SO MTR, -UUO- LPMR SO MTR, + +703 * - -UUO- == +704 * - UMOVE == +705 * - UMOVEM == +706 * - -UUO- == +707 * - -UUO- == +710 + DO PTR, DO_PTR (Special KI hack) +710-715 * - IO == +716,717 * - BLT == +720-725 * - IO == +726-777 * - -UUO- == +*/ + +/* Instruction dispatch: + +The entire range of IO instructions (7xx) is treated merely as a bunch +of normal opcodes initially. Once vectored, there are three +possibilities: + + (1) General PDP-10 instruction (e.g. PMOVE). + No special handling other than user-IOT checking. + Function defined with insdef(). 3 args: OP, AC, E. + (2) AC-dispatch internal device instruction. + Does a second dispatch after combining AC with opcode. + Function defined with ioinsdef(). 1 arg: E. + (3) Dev-IO instruction. + i_iodisp() puts opcode & AC back together to decipher as + an dev-IO instruction, and executes it for the given device + as found in devtab[device-#]. + This dispatches through the device vector using one of 4 + functions (cono, coni, datao, datai). See the device structure + definition in "kn10dev.h" for their exact prototypes. +*/ + +#if ! KLH10_CPU_KS /* All machines except KS */ + +enum { /* Low 3 bits of AC in Dev-IO instr are the IO opcode dispatch */ + IDV_BLKI=0, IDV_DATAI, + IDV_BLKO, IDV_DATAO, + IDV_CONO, IDV_CONI, + IDV_CONSZ, IDV_CONSO +}; +#endif /* !KS */ + +#if ! KLH10_CPU_KS /* All machines except KS */ + +/* "Dev" IO instr dispatch +** Does dispatch of traditional IO instructions by device, +** as opposed to internal-device dispatch (i_io###disp) which uses +** AC field as a subvector. +** +** Note that input instructions (BLKI,DATAI,CONI) need to be particularly +** careful about resolving their memory ref *prior* to doing I/O or the +** data will be lost if a page fault happens. +*/ +extern struct device *devtab[128]; /* From kn10dev.c */ + +insdef(i_diodisp) +{ + register struct device *dev; + register vmptr_t vp; + register w10_t w; + + /* Don't allow users to hack IO instrs unless User-IOT is set */ + if ((PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO) +#if KLH10_CPU_KI || KLH10_CPU_KL /* On KI+KL, devs >= 740 are OK */ + && (op < 0774)) + || (!PCFTEST(PCF_USR) && PCFTEST(PCF_PUB) /* Supv mode illegal */ +#endif + )) + return i_muuo(op, ac, e); + + /* Find device vector */ + dev = devtab[((op&077)<<1) | ((ac>>3)&01)]; + switch (ac & 07) { + + /* Handle BLKI and BLKO. Note special use of First-Part-Done flag + ** similar to that for byte pointers, in case a page fault happens + ** during the actual data transfer. The PRM does not explicitly mention + ** this use, but that seems to be what real machines do. + ** The BLKI/O pointer needs to be updated so the PDP-10 pagefail + ** handler can figure out what's going on if it wants to. + */ + case IDV_BLKI: + case IDV_BLKO: /* For now, share BLKI code. Later can + ** duplicate it if desired for speed. */ + vp = vm_modmap(e); + w = vm_pget(vp); /* Get AOBJN pointer */ + + if (!PCFTEST(PCF_FPD)) { /* Unless pointer already inc'd, */ +#if KLH10_CPU_KA + w10_t tmp = W10XWD(1,1); /* KA adds 1,,1 */ + op10m_add(w, tmp); +#else + LRHSET(w, (LHGET(w)+1)&H10MASK, (RHGET(w)+1)&H10MASK); +#endif + vm_pset(vp, w); /* Store back inc'd pointer */ + } + + /* Now generate local-section address from RH of pointer */ +#if KLH10_EXTADR + /* Confirm later that this code is correct. + ** Default section # is that of the pointer; should it be PC? + */ +#endif + /* Make local address in E out of pointer RH */ + va_lmake(e, va_sect(e), RHGET(w)); + if (ac == IDV_BLKI) { + vp = vm_modmap(e); /* See if page fault first */ + vm_pset(vp, (*(dev->dv_datai))(dev)); /* Do DATAI */ + } else + (*(dev->dv_datao))(dev, vm_read(e)); /* Do DATAO */ + + if (LHGET(w)) /* Skip return unless LH 0 */ + return PCINC_2; + break; + case IDV_DATAI: + vp = vm_modmap(e); /* Check access first */ + vm_pset(vp, (*(dev->dv_datai))(dev)); + break; + case IDV_DATAO: + (*(dev->dv_datao))(dev, vm_read(e)); + break; + case IDV_CONO: + (*(dev->dv_cono))(dev, (h10_t)va_insect(e)); + break; + case IDV_CONI: + vp = vm_modmap(e); /* Check access first */ + vm_pset(vp, (*(dev->dv_coni))(dev)); + break; + case IDV_CONSZ: + w = (*(dev->dv_coni))(dev); /* Do CONI even if E=0 */ +#if 1 + if (!(va_insect(e) & RHGET(w))) +#else /* See IO_SZ_APR for comments on this */ + if (va_insect(e) && !(va_insect(e) & RHGET(w))) +#endif + return PCINC_2; /* Skip return */ + break; + case IDV_CONSO: + w = (*(dev->dv_coni))(dev); /* Do CONI even if E=0 */ + if (RHGET(w) & va_insect(e)) + return PCINC_2; /* Skip return */ + break; + } + return PCINC_1; +} + +#endif /* !KS */ + +/* PDP-10 opcodes of 7xx come here if there is no "normal" instruction +** defined for them. This is where the secondary dispatch takes place, +** based either on their AC field (for internal-device IO instrs) or +** on their device code. +** KA only has device dispatch. +** KI and KL have both. +** KS has only AC-field dispatch. +*/ + +/* Define subvector dispatcher. One of these is defined for each +** normal-op opcode that is AC-dispatched. They could all be combined +** into the "generic" dispatcher, but having them separate retains the +** information we already have by virtue of the primary dispatch (ie 9-bit +** opcode value) and lets us avoid a reference and shift/mask of the "op" +** argument and thus is a tiny bit faster. +*/ + +#define iosubvec_define(name, opcod) \ + insdef(name) \ + { \ + /* Don't allow users to hack IO instrs unless User-IOT is set */\ + if ((!PCFTEST(PCF_USR) || PCFTEST(PCF_UIO)) \ + && opciortn[(((opcod)&077)<<4)+ac]) \ + return (*opciortn[(((opcod)&077)<<4)+ac])(e); \ + else return i_muuo(op, ac, e); \ + } + +#if 0 +iosubvec_define(i_iodisp, op) /* Generic dispatcher for AC-IO instrs */ +#endif /* 0 */ /* Not needed but saved for posterity */ + +iosubvec_define(i_io700disp, 0700) +iosubvec_define(i_io701disp, 0701) +iosubvec_define(i_io702disp, 0702) + + +#if KLH10_SYS_ITS && KLH10_CPU_KS + +/* ITS I/O instructions. +*/ + +/* IORDI (0710) - Read Unibus #3 word at E into AC +*/ +insdef(i_iordi) +{ + register uint32 reg; + + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) return i_muuo(op, ac, e); + reg = uba_read(&dvub3, (dvuadr_t)va_insect(e)); + ac_setlrh(ac, (reg>>18), reg & H10MASK); + return PCINC_1; +} + +/* IORDQ (0711) - Read Unibus #1 word at E into AC +*/ +insdef(i_iordq) +{ + register uint32 reg; + + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) return i_muuo(op, ac, e); + reg = uba_read(&dvub1, (dvuadr_t)va_insect(e)); + ac_setlrh(ac, (reg>>18), reg & H10MASK); + return PCINC_1; +} + +/* IOWRI (0714) - Write Unibus #3 word from AC into E +*/ +insdef(i_iowri) +{ + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) return i_muuo(op, ac, e); + uba_write(&dvub3, (dvuadr_t)va_insect(e), ac_getrh(ac)); + return PCINC_1; +} + +/* IOWRQ (0715) - Write Unibus #1 word from AC into E +*/ +insdef(i_iowrq) +{ + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) return i_muuo(op, ac, e); + uba_write(&dvub1, (dvuadr_t)va_insect(e), ac_getrh(ac)); + return PCINC_1; +} + +/* IORD (0712) - Read Unibus word into AC from IO addr in c(E) +*/ +insdef(i_iord) +{ + register uint32 reg; + + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) return i_muuo(op, ac, e); + reg = ub_read(vm_read(e)); + ac_setlrh(ac, (reg>>18), reg & H10MASK); + return PCINC_1; +} + +/* IOWR (0713) - Write Unibus word from AC to IO addr in c(E) +*/ +insdef(i_iowr) +{ + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) return i_muuo(op, ac, e); + ub_write(vm_read(e), ac_getrh(ac)); + return PCINC_1; +} + + +/* Byte-mode IO is only used three times in all of ITS: +** all in TS3TTY for the DZ-11. +** IORDBI B,%DZRTC(C) +** IOWRBI A,%DZRTD(C) +** IOWRBI B,%DZRTC(C) +*/ + +/* IORDBI (0720) - Read Unibus #3 byte at E into AC +*/ +insdef(i_iordbi) +{ + register uint32 reg; + + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) return i_muuo(op, ac, e); + reg = va_isodd(e) + ? (uba_read(&dvub3, (dvuadr_t)va_insect(e) & ~01) >> 8) + : uba_read(&dvub3, (dvuadr_t)va_insect(e)); + ac_setlrh(ac, 0, reg & 0377); + return PCINC_1; +} + +/* IOWRBI (0724) - Write Unibus #3 byte from AC into E +** Note that a word is first read, then written! +*/ +insdef(i_iowrbi) +{ + register h10_t mask, val; + register dvuadr_t uadr; + + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) return i_muuo(op, ac, e); + val = ac_getrh(ac) & 0377; + if (va_isodd(e)) { mask = ~(0377 << 8); val <<= 8; } + else mask = ~0377; + uadr = va_insect(e) & ~(dvuadr_t)01; + uba_write(&dvub3, uadr, (uba_read(&dvub3, uadr) & mask) | val); + return PCINC_1; +} + +/* IORDBQ (0721) - Read Unibus #1 byte at E into AC +*/ +insdef(i_iordbq) +{ + register uint32 reg; + + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) return i_muuo(op, ac, e); + reg = va_isodd(e) + ? (uba_read(&dvub1, (dvuadr_t)va_insect(e) & ~01) >> 8) + : uba_read(&dvub1, (dvuadr_t)va_insect(e)); + ac_setlrh(ac, 0, reg & 0377); + return PCINC_1; +} + +/* IOWRBQ (0725) - Write Unibus #1 byte from AC into E +** Note that a word is first read, then written! +*/ +insdef(i_iowrbq) +{ + register h10_t mask, val; + register dvuadr_t uadr; + + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) return i_muuo(op, ac, e); + val = ac_getrh(ac) & 0377; + if (va_isodd(e)) { mask = ~(0377 << 8); val <<= 8; } + else mask = ~0377; + uadr = va_insect(e) & ~(dvuadr_t)01; + uba_write(&dvub1, uadr, (uba_read(&dvub1, uadr) & mask) | val); + return PCINC_1; +} + +/* IORDB (0722) - Read Unibus byte into AC from IO addr in c(E) +*/ +insdef(i_iordb) +{ + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) return i_muuo(op, ac, e); + ac_setlrh(ac, 0, ub_bread(vm_read(e))); + return PCINC_1; +} + +/* IOWRB (0723) - Write Unibus byte from AC to IO addr in c(E) +*/ +insdef(i_iowrb) +{ + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) return i_muuo(op, ac, e); + ub_bwrite(vm_read(e), ac_getrh(ac)); + return PCINC_1; +} +#endif /* ITS && KS */ + +#if (KLH10_SYS_T10 || KLH10_SYS_T20) && KLH10_CPU_KS + +/* DEC I/O instructions. +*/ + +/* IOEACALC - Auxiliary to painfully re-calculate E for IO instructions. +** Assumes instruction is at PC or at end of an XCT chain starting +** at PC. This will not work for IO instructions executed as +** trap or interrupt instructions, nor PXCT operands! All of which +** are probably undefined anyway. +*/ +static w10_t ioeacalc() +{ + register vaddr_t ea; + register w10_t w; + + /* Assume that our execution started at PC. This will be untrue + ** only if we're being executed as a trap or interrupt instruction. + ** XCT is allowed, but PXCT is fortunately illegal. + */ + ea = PC_VADDR; + for (;;) { + w = vm_fetch(ea); /* Get instr */ + if ((LHGET(w) >> 9) != I_XCT) + break; + ea = ea_calc(w); /* Is XCT, track chain */ + } + + /* Now have retrieved original instruction word, re-compute + ** its E using special algorithm. + */ + if (iw_i(w)) { /* Indirection? */ + va_lmake(ea, 0, + (RHGET(w) + (iw_x(w) /* Indirection & indexing? */ + ? ac_getrh(iw_x(w)) + : 0)) & H10MASK); + w = vm_fetch(ea); /* Get contents of word pointed to */ + } else { + if (iw_x(w)) { /* Indexing? */ + register h10_t y; + y = RHGET(w); /* Yes, remember Y */ + w = ac_get(iw_x(w)); /* Get the X reg */ + if (op10m_skipge(w)) /* If its LH is positive, */ + op10m_addi(w, y); /* add Y into whole word */ + else /* Else neg, just get local X+Y */ + LRHSET(w, 0, (RHGET(w)+y)&H10MASK); + } else + LHSET(w, 0); /* No I or X, just return Y */ + } + return w; /* Done, return whole-word address */ +} + + +/* TIOE (0710) - Test IO Equal +*/ +insdef(i_tioe) +{ + register w10_t w; + + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) return i_muuo(op, ac, e); + w = ac_get(ac); + return ((((uint32)LHGET(w)<<18) | RHGET(w)) & ub_read(ioeacalc()) ) + ? PCINC_1 : PCINC_2; /* Skip if no AC bits are set */ +} + +/* TION (0711) - Test IO Not Equal +*/ +insdef(i_tion) +{ + register w10_t w; + + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) return i_muuo(op, ac, e); + w = ac_get(ac); + return ((((uint32)LHGET(w)<<18) | RHGET(w)) & ub_read(ioeacalc()) ) + ? PCINC_2 : PCINC_1; /* Skip if some AC bits are set */ +} + +/* RDIO (0712) - Read IO +*/ +insdef(i_rdio) +{ + register uint32 reg; + + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) return i_muuo(op, ac, e); + + /* Compute IO address and make read request */ + reg = ub_read(ioeacalc()); /* Make the read request */ + ac_setlrh(ac, (reg>>18), reg & H10MASK); + return PCINC_1; +} + +/* WRIO (0713) - Write IO +*/ +insdef(i_wrio) +{ + register w10_t w; + + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) return i_muuo(op, ac, e); + + /* Fetch IO address and make write request */ + w = ac_get(ac); + ub_write(ioeacalc(), /* (LHGET(w)<<18)| */ RHGET(w)); + return PCINC_1; +} + +/* BSIO (0714) - Bit Set IO +** Only deals with low 16 bits. +*/ +insdef(i_bsio) +{ + register w10_t ioaddr; + + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) return i_muuo(op, ac, e); + ioaddr = ioeacalc(); + ub_write(ioaddr, (h10_t)(ub_read(ioaddr) | (ac_getrh(ac) & MASK16))); + return PCINC_1; +} + +/* BCIO (0715) - Bit Clear IO +** Only deals with low 16 bits. +*/ +insdef(i_bcio) +{ + register w10_t ioaddr; + + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) return i_muuo(op, ac, e); + ioaddr = ioeacalc(); + ub_write(ioaddr, (h10_t)(ub_read(ioaddr) & ~(ac_getrh(ac) & MASK16))); + return PCINC_1; +} + + +/* TIOEB (0720) - Test IO Equal, Byte +*/ +insdef(i_tioeb) +{ + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) return i_muuo(op, ac, e); + return (ac_getrh(ac) & ub_bread(ioeacalc())) ? PCINC_1 : PCINC_2; +} + +/* TIONB (0721) - Test IO Not Equal, Byte +*/ +insdef(i_tionb) +{ + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) return i_muuo(op, ac, e); + return (ac_getrh(ac) & ub_bread(ioeacalc())) ? PCINC_2 : PCINC_1; +} + +/* RDIOB (0722) - Read IO Byte +*/ +insdef(i_rdiob) +{ + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) return i_muuo(op, ac, e); + + /* Fetch IO address and make read request */ + ac_setlrh(ac, 0, ub_bread(ioeacalc())); + return PCINC_1; +} + +/* WRIOB (0723) - Write IO Byte +*/ +insdef(i_wriob) +{ + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) return i_muuo(op, ac, e); + + /* Fetch IO address and make write request */ + ub_bwrite(ioeacalc(), ac_getrh(ac)); + return PCINC_1; +} + +/* BSIOB (0724) - Bit Set IO Byte +*/ +insdef(i_bsiob) +{ + register w10_t ioaddr; + + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) return i_muuo(op, ac, e); + ioaddr = ioeacalc(); + ub_bwrite(ioaddr, (h10_t)(ub_bread(ioaddr) | (ac_getrh(ac) & 0377))); + return PCINC_1; +} + +/* BCIOB (0725) - Bit Clear IO Byte +*/ +insdef(i_bciob) +{ + register w10_t ioaddr; + + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) return i_muuo(op, ac, e); + ioaddr = ioeacalc(); + ub_bwrite(ioaddr, (h10_t)(ub_bread(ioaddr) & ~(ac_getrh(ac) & 0377))); + return PCINC_1; +} + +#endif /* (T10 || T20) && KS */ diff --git a/src/injrst.c b/src/injrst.c new file mode 100644 index 0000000..bac5233 --- /dev/null +++ b/src/injrst.c @@ -0,0 +1,757 @@ +/* INJRST.C - Jump (and Stack) instruction routines +*/ +/* $Id: injrst.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: injrst.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + + +/* See CODING.TXT for guidelines to coding instruction routines. */ + +#include "klh10.h" +#include "kn10def.h" +#include "kn10ops.h" +#include /* For debug output */ + +#ifdef RCSID + RCSID(injrst_c,"$Id: injrst.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Imported functions */ + +extern void pishow(FILE *); +extern void pcfshow(FILE *, h10_t flags); + + + +insdef(i_jffo) /* JFFO */ +{ + register int i = op10ffo(ac_get(ac)); + if (i < 36) { /* Found a bit? */ + ac_setlrh(ac_off(ac,1), 0, i); /* Yep, set AC+1 */ + PC_JUMP(e); /* and take the jump! */ + return PCINC_0; + } + ac_setlrh(ac_off(ac,1), 0, 0); /* Empty, don't jump */ + return PCINC_1; +} + +insdef(i_jfcl) +{ + if (ac) { /* See which flags we're asking for */ + register h10_t flags; + + flags = (h10_t)ac << (18-4); /* Shift to match PC flags */ + if (PCFTEST(flags)) { /* Any of them match? */ + PCFCLEAR(flags); /* Yes, clear them */ + PC_JUMP(e); /* and take the jump! */ + return PCINC_0; + } + } + return PCINC_1; +} + + +insdef(i_jsr) /* JSR */ +{ + register w10_t w; + + PC_1WORD(w); /* Make PC-word in w (PC+1, plus flags if sect 0) */ + vm_write(e, w); /* Store it at E */ + PCFCLEAR(PCF_FPD|PCF_AFI|PCF_TR2|PCF_TR1); /* Clear these flags */ + va_inc(e); /* Jump to E+1 (local or global) */ + PC_JUMP(e); + return PCINC_0; +} + +insdef(i_jsp) /* JSP */ +{ + register w10_t w; + + PC_1WORD(w); /* Cons up PC+1 word in w */ + ac_set(ac, w); /* Store it in AC */ + PCFCLEAR(PCF_FPD|PCF_AFI|PCF_TR2|PCF_TR1); /* Clear these flags */ + PC_JUMP(e); /* Jump to E */ + return PCINC_0; +} + +/* Note for JSA and JRA that there is no difference in their behavior +** when executed in non-zero sections. +*/ +insdef(i_jsa) /* JSA */ +{ + register w10_t w; + + vm_write(e, ac_get(ac)); /* Store c(AC) into E */ + PC_XWDPC1(w, va_insect(e)); /* Cons up word: ,, */ + ac_set(ac, w); /* Store that JSA-word in AC */ + va_inc(e); /* Jump to E+1 */ + PC_JUMP(e); + return PCINC_0; +} + +insdef(i_jra) /* JRA */ +{ + register vaddr_t va; + + va_lmake(va, PC_SECT,ac_getlh(ac)); /* Make AC.lh a local address */ + ac_set(ac, vm_read(va)); /* Get c(AC.lh) into AC */ + PC_JUMP(e); /* Jump to E */ + return PCINC_0; +} + +/* JRST hackery */ + +/* Declare subvariants of JRST */ +pcinc_t ij_jrstf (int, int, vaddr_t); +pcinc_t ij_halt (int, int, vaddr_t); +pcinc_t ij_jen (int, int, vaddr_t); +pcinc_t ij_jrst10(int, int, vaddr_t); +#if !KLH10_SYS_ITS +pcinc_t ij_portal(int, int, vaddr_t); +pcinc_t ij_xjrstf(int, int, vaddr_t); +pcinc_t ij_xjen (int, int, vaddr_t); +pcinc_t ij_xpcw (int, int, vaddr_t); +pcinc_t ij_sfm (int, int, vaddr_t); +#endif +#if KLH10_CPU_KLX +pcinc_t ij_xjrst (int, int, vaddr_t); +#endif + + +insdef(i_jrst) +{ + switch (ac) { + case 0: /* JRST */ + PC_JUMP(e); + return PCINC_0; + +#if !KLH10_SYS_ITS + case 1: return ij_portal(op, ac, e); /* PORTAL - not used by ITS */ +#endif /* DEC */ + case 2: return ij_jrstf(op, ac, e); /* JRSTF - Used */ + case 4: return ij_halt(op, ac, e); /* HALT - Used */ +#if !KLH10_SYS_ITS && (KLH10_CPU_KS||KLH10_CPU_KLX) + case 5: return ij_xjrstf(op, ac, e); /* XJRSTF - not used by ITS */ + case 6: return ij_xjen(op, ac, e); /* XJEN - not used by ITS */ + case 7: return ij_xpcw(op, ac, e); /* XPCW - not used by ITS */ +#endif /* DEC KS || KLX */ + case 010: return ij_jrst10(op, ac, e); /* JRST 10, - used once (?) */ + case 012: return ij_jen(op, ac, e); /* JEN - used frequently */ +#if !KLH10_SYS_ITS && (KLH10_CPU_KS||KLH10_CPU_KLX) + case 014: return ij_sfm(op, ac, e); /* SFM - not used by ITS */ +# if KLH10_CPU_KLX + case 015: return ij_xjrst(op, ac, e); /* XJRST - not used by ITS */ +# endif /* KLX */ +#endif /* DEC */ +#if KLH10_SYS_ITS + case 017: break; /* - used once by KL10 ITS */ +#endif + } + /* Illegal - MUUO (3,11,13,16,17) */ + return i_muuo(op, ac, e); +} + + +/* DORSTF - Actually carry out flag restoration for JRSTF/XJRSTF/XJEN/XPCW. +** Restore flags from the top 12 bits of the word in w. +** +** All flags are restored verbatim with these exceptions: +** USER cannot be cleared by a new setting of 0 (no effect), although +** a 1 will always set it. +** UIO can always be cleared, but new setting of 1 will only have +** an effect if the old mode is EXEC. (Note that if old +** mode is user, 1 merely has no effect; the bit may already +** be set and will remain so.) +** PUBLIC can always be set by a 1, but a 0 only has effect if +** old mode is EXEC and new mode is USER. +** +** No special actions are taken for PrevCtxtPublic or PrevCtxtUser. +** +** The wording of the DEC PRM (6/82 p.2-71) is a little misleading, as it +** implies that bit 6 (User-IO) cannot be set to 1 while in user mode; however, +** it appears in reality that it *can* be, if it was *already* 1. +** This is a rather subtle point. +*/ + /* LH(w) contains flags to restore */ +#if KLH10_EXTADR +static pcinc_t dorstf(register w10_t w, vaddr_t e, int pcsf) +# define IJ_DO_RSTF(w, e, pcsf) dorstf(w, e, pcsf) +#else +static pcinc_t dorstf(register w10_t w, vaddr_t e) +# define IJ_DO_RSTF(w, e, pcsf) dorstf(w, e) +#endif +{ + register uint18 newf; /* New flags to restore */ + +#if KLH10_DEBUG + if (cpu.fe.fe_debug) { + putc('[', stderr); + pishow(stderr); pcfshow(stderr, cpu.mr_pcflags); +#if KLH10_EXTADR + if (pcsf) fprintf(stderr, "(PCS:%o)", pag_pcsget()); +#endif + fprintf(stderr,"%lo: -> ", (long) PC_30); + } +#endif + newf = LHGET(w); /* Get flags into easy-access reg */ + + if (PCFTEST(PCF_USR)) { /* Currently in user mode? */ + newf |= PCF_USR; /* Never permit USR clearing */ + if (!PCFTEST(PCF_UIO)) /* If not already in UIO, */ + newf &= ~PCF_UIO; /* don't permit UIO setting */ + } +#if KLH10_CPU_KL || KLH10_CPU_KI + if (PCFTEST(PCF_PUB) /* If trying to clear Public, */ + && (newf & PCF_PUB)==0) { /* Must be EXEC, setting USER too. */ + if (PCFTEST(PCF_USR) || !(newf&PCF_USR)) + newf |= PCF_PUB; /* Nope, don't clear it */ + } +#endif /* KL || KI */ + + /* For XJRSTF/XJEN, a new PCS is loaded from the flag word only if + ** the new flags specify EXEC mode. + ** (PRM 2-66, 2nd sentence from bottom). + */ +#if KLH10_EXTADR /* If going into EXEC mode, restore PCS */ + if (pcsf && !(newf & PCF_USR)) { + pag_pcsset(RHGET(w) & UBR_PCS); /* For now, ignore too-big PCS */ + } +#endif + + cpu.mr_pcflags = newf & PCF_MASK; /* Set new flags!! */ + PC_JUMP(e); /* Take the jump... */ + apr_pcfcheck(); /* Check flags for any changes. */ + /* NOTE! It is important to check AFTER the PC has changed, so that + ** if there is a user/exec context change, the saved JPC will be the + ** appropriate one for the previous context. + */ +#if KLH10_DEBUG + if (cpu.fe.fe_debug) { + pishow(stderr); pcfshow(stderr, cpu.mr_pcflags); +#if KLH10_EXTADR + if (pcsf) fprintf(stderr, "(PCS:%o)", pag_pcsget()); +#endif + fprintf(stderr,"%lo:]\r\n", (long) PC_30); + } +#endif + return PCINC_0; +} + +#if !KLH10_SYS_ITS +/* PORTAL (JRST 1,) - Clear Public and jump to E +** On KS10, equivalent to JRST. +*/ +insdef(ij_portal) +{ +#if KLH10_CPU_KI || KLH10_CPU_KL + PCFCLEAR(PCF_PUB); /* Always clear public, nothing else needed */ +#endif + PC_JUMP(e); + return PCINC_0; +} +#endif /* !KLH10_SYS_ITS */ + +/* JRSTF (JRST 2,)- Jump and Restore Flags from final word used in calc of E. +** +** Because this word isn't saved by the normal instruction loop, +** it needs to be recomputed. This is painful, but I judged that +** it was better to make JRSTF slower so that every other instruction could +** be slightly faster. +** +** The "mr_injrstf" flag is set solely so any page faults which occur can +** avoid being faked out. No faults should in fact happen at this point; +** if any do there is a horrible error somewhere. +*/ + +insdef(ij_jrstf) +{ + register w10_t w; + register vaddr_t ea; +#if KLH10_EXTADR + if (PC_ISEXT) /* A JRSTF in non-zero section is a MUUO */ + return i_muuo(op, ac, e); +#endif + cpu.mr_injrstf = TRUE; + + /* Assume that our execution started at mr_PC. This will be untrue + ** only if we're being executed as a trap or interrupt instruction. + ** XCT is allowed, but PXCT is fortunately illegal. + */ + ea = PC_VADDR; /* Get PC as a virtual address */ + for (;;) { + w = vm_fetch(ea); /* Get instr */ + if (iw_op(w) != I_XCT) + break; + ea = ea_calc(w); /* Is XCT, track chain */ + } + if (iw_op(w) != op /* Must match original instr */ + || iw_ac(w) != ac) /* both in OP and AC */ + panic("ij_jrstf: cannot duplicate op"); + + /* Now get to the point of this stupid exercise! */ + w = ea_wcalc(w, cpu.acblk.xea, cpu.vmap.xea); /* Do full EA calc */ + if (RHGET(w) != va_insect(e)) /* One more check... */ + panic("ij_jrstf: cannot duplicate E"); + cpu.mr_injrstf = FALSE; /* No more pagefault risk */ + return IJ_DO_RSTF(w, e, FALSE); +} + +/* JEN (JRST 12,) - Combination of JRST10 and JRSTF. +** Lets those routines do the work. JRSTF's hack to find the PC flags +** will work, because the opcode & AC are all passed along to it. +** If user mode, only legal if User-IOT is set. +*/ +insdef(ij_jen) +{ +#if KLH10_EXTADR + if (PC_ISEXT) + return i_muuo(op, ac, e); +#endif +#if KLH10_CPU_KS || KLH10_CPU_KA + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) +#elif KLH10_CPU_KL + if ((PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) /* User-IOT or Kernel */ + || (!PCFTEST(PCF_USR) && PCFTEST(PCF_PUB))) +#elif KLH10_CPU_KI + if (PCFTEST(PCF_USR | PCF_PUB)) /* Kernel mode only */ +#endif + return i_muuo(op, ac, e); + pi_dismiss(); /* Tell PI to dismiss current int */ + return ij_jrstf(op, ac, e); +} + +/* JRST 10, - Dismisses current interrupt ("restores the level on which the +** highest priority interrupt is currently being held"). +** If user mode, only legal if User-IOT is set. +*/ +insdef(ij_jrst10) +{ +#if KLH10_CPU_KS || KLH10_CPU_KA + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) /* User-IOT or Exec */ +#elif KLH10_CPU_KL + if ((PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) /* User-IOT or Kernel */ + || (!PCFTEST(PCF_USR) && PCFTEST(PCF_PUB))) +#elif KLH10_CPU_KI + if (PCFTEST(PCF_USR | PCF_PUB)) /* Kernel mode only */ +#endif + return i_muuo(op, ac, e); + + pi_dismiss(); /* Tell PI to dismiss current int */ + PC_JUMP(e); /* Take the jump... */ + +#if KLH10_DEBUG + if (cpu.fe.fe_debug) { + fprintf(stderr,"[ -> "); + pishow(stderr); pcfshow(stderr, cpu.mr_pcflags); + fprintf(stderr,"%lo:]\r\n", (long) PC_30); + } +#endif + return PCINC_0; +} + +/* HALT (JRST 4,) - Halt the processor. +*/ +insdef(ij_halt) +{ +#if KLH10_CPU_KS || KLH10_CPU_KA + if (!PCFTEST(PCF_USR)) /* Exec mode can halt */ +#elif KLH10_CPU_KL || KLH10_CPU_KI + if (!PCFTEST(PCF_USR|PCF_PUB)) /* Only Kernel mode can halt */ +#endif + { + cpu.mr_haltpc = cpu.mr_PC; /* Remember loc of the HALT instr */ + PC_JUMP(e); /* Update PC as if jumped */ + apr_halt(HALT_PROG); /* Halt processor! */ + /* Never returns */ + } + return i_muuo(op, ac, e); +} + +/* DEC extended variants of JRST, never used on ITS or a single-section KL */ + +#if !KLH10_SYS_ITS && (KLH10_CPU_KS||KLH10_CPU_KLX) + +/* XJRSTF (JRST 5,) +** Pretty much the same as JRSTF except the flags and PC come from +** a pair of memory locations. On the KS10 no section addressing +** is possible (wonder what happens if tried on a real KS10?) +** +** XJRSTF and XJEN don't save anything. +** A new PCS is loaded from the flag word only if a KLX and the new +** flags specify exec mode (PRM 2-66, 2nd sentence from bottom). +** The new flags are used verbatim, except for the same restrictions +** as JRSTF (that is, USER, UIO, PUBLIC flags are special-cased). +** (PRM 2-71) +** See dorstf() for more comments. +*/ +insdef(ij_xjrstf) +{ + register dw10_t dpcw; + + vm_dread(e, dpcw); /* Fetch double-wd from E, E+1 (may pageflt) */ + va_lfrword(e, LOGET(dpcw)); /* New PC from c(E+1) */ + return IJ_DO_RSTF(HIGET(dpcw), e, TRUE); /* Use flags from c(E) */ +} + +/* XJEN (JRST 6,) +** See comments for XJRSTF -- essentially the same. +** To XJRSTF as JEN is to JRSTF, but only allowed in Exec mode. +** However, one special precaution is taken; the PCW at E, E+1 is fetched +** prior to restoring an interrupt level, to ensure any page fault +** happens before clobbering the PI status! +** This is why the XJRSTF code is duplicated, rather than calling +** it after dismissing the interrupt. +*/ +insdef(ij_xjen) +{ + register dw10_t dpcw; + +#if KLH10_CPU_KS + if (PCFTEST(PCF_USR)) /* Ensure we're in exec mode */ +#elif KLH10_CPU_KLX + if ((PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) /* User-IOT or Kernel */ + || (!PCFTEST(PCF_USR) && PCFTEST(PCF_PUB))) +#endif + return i_muuo(op, ac, e); + + vm_dread(e, dpcw); /* Fetch double-wd from E, E+1 (may pageflt) */ + pi_dismiss(); /* Tell PI to dismiss current int */ + va_lfrword(e, LOGET(dpcw)); /* New PC from E+1 */ + return IJ_DO_RSTF(HIGET(dpcw), e, TRUE); /* Use flags from E */ +} + +/* XPCW (JRST 7,) +** Note special precaution as for XJEN to ensure any page faults happen +** prior to changing flags or PC. +** +** PCS is saved in the flag word ONLY if old mode was exec (the flag +** test is on old flags, not new ones). +** No new PCS is set. +** The new flags are taken from the new flag word, subject to +** same restrictions as JRSTF. It doesn't appear that +** either PCP or PCU is specially set. +** PRM says XPCW should only be used as interrupt instr, but T20 +** monitor does use it as regular instr (in SCHED). +** This code only implements its "regular" behavior; the KLH10 +** PI interrupt code special-cases XPCW itself and never calls +** this routine. +*/ +insdef(ij_xpcw) +{ + register w10_t w; + register vaddr_t e2; + dw10_t dpcw; + +#if KLH10_CPU_KS + if (PCFTEST(PCF_USR)) /* Ensure we're in exec mode */ +#elif KLH10_CPU_KLX + if ((PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) /* User-IOT or Kernel */ + || (!PCFTEST(PCF_USR) && PCFTEST(PCF_PUB))) +#endif + return i_muuo(op, ac, e); + + e2 = e; + va_add(e2, 2); /* Get E+2 */ + vm_dread(e2, dpcw); /* Fetch double from E+2, E+3 (may pageflt) */ + + LRHSET(w, cpu.mr_pcflags, 0); /* Set up 1st word of PCW */ +#if KLH10_EXTADR + if (!PCFTEST(PCF_USR)) /* If in exec mode */ + RHSET(w, pag_pcsget()); /* then OK to add pager's PrevContextSection */ +#endif + vm_write(e, w); /* Store in E */ + PC_1TOWORD(w); /* Set up PC+1 word */ + va_inc(e); + vm_write(e, w); /* Store in E+1 */ + + va_lfrword(e2, LOGET(dpcw)); /* New PC from E+3 */ + return IJ_DO_RSTF(HIGET(dpcw), e2, FALSE); /* Use flags from E+2 */ +} + +/* SFM (JRST 14,) +** This is now legal in user mode, but the PCS info is only stored if in +** exec mode. (PRM 2-72) +** Additionally, PRM 2-73 says on an extended KL, SFM is only legal in +** NZ section. However, this was changed as of KL ucode 331 to +** allow 0-section as well, and there is no IO-legal test; +** PCS is still only saved if in exec mode, though. +*/ +insdef(ij_sfm) +{ + register w10_t w; + +#if KLH10_CPU_KS /* KS10 requires exec mode (kernel) */ + if (PCFTEST(PCF_USR)) /* Ensure we're in exec mode */ + return i_muuo(op, ac, e); +#endif + + /* Don't need to mung flags since not changing modes */ + LRHSET(w, cpu.mr_pcflags, 0); /* Set up 1st word of PCW */ +#if KLH10_EXTADR + if (!PCFTEST(PCF_USR)) /* If in exec mode, */ + RHSET(w, pag_pcsget()); /* add PCS to the flag word */ +#endif + vm_write(e, w); /* Store in E */ + return PCINC_1; /* That's all, no jump! */ +} + +#if KLH10_CPU_KLX + +/* XJRST (JRST 15,) +** This is yet another late addition to the KL that was never +** documented in the PRM. It seems to have been added as of ucode +** 301 and is used extensively in the T20 monitor. +** Basically appears to jump to c(E) rather than E, treating +** the contents of E as a full 30-bit global virtual address. It's +** not clear whether the high 6 bits matter, but I'll assume they are +** ignored. +** This also appears to be legal in any section, in any mode. +** Not a bad idea, actually. +*/ + +insdef(ij_xjrst) +{ + register w10_t w; + register vaddr_t va; + + w = vm_read(e); /* Fetch c(E) */ + va_gfrword(va, w); /* Turn into a virtual address */ + PC_JUMP(va); /* Jump there! */ + return PCINC_0; +} +#endif /* KLX */ + +#endif /* !ITS && (KS || KLX) */ + +/* Stack instructions */ + +/* Note: on the KA10 the push and pop operations are done by +** adding or subtracting 1,,1 as a word entity (ie not independent halves). +** Also, there are no trap flags, so Pushdown Overflow is set instead. +** These actions are not yet coded for, to avoid clutter. +*/ + +/* Note special page map context references for stack instructions. +** These are so PXCT can work by twiddling the context pointers. +** For: +** Computing E - normal ea_calc() using XEA map +** R/W of c(E) for PUSH, POP - normal XRW map +** R/W of stack data - special XBRW map +*/ +#define vm_stkread(a) (vm_pget(vm_xbrwmap((a), VMF_READ))) +#define vm_stkwrite(a, w) (vm_pset(vm_xbrwmap((a), VMF_WRITE), (w))) +#define vm_stkmap(a, f) vm_xbrwmap((a), (f)) + +/* No other special actions are needed for PXCT; the test for running +** extended is always made using PC section (regardless of context). +** However, on an extended KL Uhler claims stack references should +** always be made in the current context. What isn't clear is whether +** a KLX is expected to ignore the stack-context AC bit (12), or if it +** does undefined things as a result. +** For generality this code does implement stack reference mapping even +** on a KLX, which corresponds to the latter option. As long as the +** monitor only uses valid AC bit combinations we'll be fine. +** +** HOWEVER - it appears from the DFKED diagnostic that bit 12 DOES +** operate in a real KL and it affects both the test for PC section AND the +** mapping used for the stack pointer. Ugh!!!! +** Don't know yet whether it is used by a real monitor. +*/ + +insdef(i_push) +{ + register w10_t w; + register vaddr_t sloc; + + w = ac_get(ac); + +#if KLH10_EXTADR + /* Determine if stack pointer is local or global */ + if (PC_ISEXT && op10m_skipge(w) && op10m_tlnn(w, VAF_SMSK)) { + /* Global format */ + op10m_inc(w); /* Increment entire word */ + va_gfrword(sloc, w); /* Get global addr from wd */ + vm_stkwrite(sloc, vm_read(e)); /* Push c(E) on stack */ + } else +#endif + { + /* Local format */ + RHSET(w, (RHGET(w)+1)&H10MASK); /* Increment RH only */ + va_lmake(sloc, PC_SECT, RHGET(w)); /* Get local address */ + vm_stkwrite(sloc, vm_read(e)); /* Push c(E) on stack */ + + /* All mem refs won, safe to store AC and test for setting Trap 2 */ + LHSET(w, (LHGET(w)+1)&H10MASK); /* Increment LH */ + if (LHGET(w) == 0) /* If became 0, */ + PCFTRAPSET(PCF_TR2); /* set Trap 2! */ + } + ac_set(ac, w); + return PCINC_1; +} + +insdef(i_pushj) +{ + register w10_t pcw, w; + register vaddr_t sloc; + + + w = ac_get(ac); + PC_1WORD(pcw); /* Make PC+1 word */ + +#if KLH10_EXTADR + + /* Determine if stack pointer is local or global */ + if (PC_ISEXT && op10m_skipge(w) && op10m_tlnn(w, VAF_SMSK)) { + /* Global format */ + op10m_inc(w); /* Increment entire word */ + va_gfrword(sloc, w); /* Get global addr from wd */ + vm_stkwrite(sloc, pcw); /* Push PC word on stack */ + PCFCLEAR(PCF_FPD|PCF_AFI|PCF_TR2|PCF_TR1); /* Clear these flags */ + } else +#endif + { + /* Local format */ + RHSET(w, (RHGET(w)+1)&H10MASK); /* Increment RH only */ + va_lmake(sloc, PC_SECT, RHGET(w)); /* Get local address */ + vm_stkwrite(sloc, pcw); /* Push PC word on stack */ + + /* All mem refs won, safe to store AC and test for setting Trap 2 */ + PCFCLEAR(PCF_FPD|PCF_AFI|PCF_TR2|PCF_TR1); /* Clear these flags */ + LHSET(w, (LHGET(w)+1)&H10MASK); /* Increment LH */ + if (LHGET(w) == 0) /* If became 0, */ + PCFTRAPSET(PCF_TR2); /* set Trap 2! */ + } + ac_set(ac, w); + PC_JUMP(e); /* Jump to new PC */ + return PCINC_0; +} + + +insdef(i_pop) +{ + register w10_t w; + register vaddr_t sloc; + + w = ac_get(ac); + +#if KLH10_EXTADR + /* Determine if stack pointer is local or global */ + if (PC_ISEXT && op10m_skipge(w) && op10m_tlnn(w, VAF_SMSK)) { + /* Global format */ + va_gfrword(sloc, w); /* Get global addr from wd */ + vm_write(e, vm_stkread(sloc)); /* Pop stack to c(E) */ + op10m_dec(w); /* Decrement entire word */ + + } else +#endif + { + /* Local format */ + va_lmake(sloc, PC_SECT, RHGET(w)); /* Get local address */ + vm_write(e, vm_stkread(sloc)); /* Pop stack to c(E) */ + + /* All mem refs won, safe to store AC and test for setting Trap 2 */ + RHSET(w, (RHGET(w)-1)&H10MASK); /* Decrement RH only */ + if (LHGET(w) == 0) { /* If decrement will wrap, */ + PCFTRAPSET(PCF_TR2); /* set Trap 2! */ + LHSET(w, H10MASK); + } else + LHSET(w, LHGET(w)-1); /* Decr LH, no mask needed */ + } + ac_set(ac, w); + return PCINC_1; +} + +insdef(i_popj) +{ + register w10_t pcw, w; + register vaddr_t sloc; + + w = ac_get(ac); + +#if KLH10_EXTADR + /* Determine if stack pointer is local or global */ + if (PC_ISEXT && op10m_skipge(w) && op10m_tlnn(w, VAF_SMSK)) { + /* Global format */ + va_gfrword(sloc, w); /* Get global addr from wd */ + pcw = vm_stkread(sloc); /* Pop PC word off stack */ + op10m_dec(w); /* Decrement entire word */ + } else +#endif + { + /* Local format */ + va_lmake(sloc, PC_SECT, RHGET(w)); /* Get local address */ + pcw = vm_stkread(sloc); /* Pop PC word off stack */ + + /* All mem refs won, safe to store AC and test for setting Trap 2 */ + RHSET(w, (RHGET(w)-1)&H10MASK); /* Decrement RH only */ + if (LHGET(w) == 0) { /* If decrement will wrap, */ + PCFTRAPSET(PCF_TR2); /* set Trap 2! */ + LHSET(w, H10MASK); + } else + LHSET(w, LHGET(w)-1); /* Decr LH, no mask needed */ + } + + /* Build correct new PC from PC word popped off */ + if (PC_ISEXT) + va_lfrword(e, pcw); + else va_lmake(e, PC_SECT, RHGET(pcw)); + + ac_set(ac, w); + PC_JUMP(e); /* Now jump to restored PC */ + return PCINC_0; +} + + +insdef(i_adjsp) +{ + register w10_t w; + register h10_t h; + + w = ac_get(ac); /* Get stack pointer */ +#if KLH10_EXTADR + /* Determine if stack pointer is local or global */ + if (PC_ISEXT && op10m_skipge(w) && op10m_tlnn(w, VAF_SMSK)) { + /* Global format */ + if (H10SIGN & va_insect(e)) { /* Negative adjustment? */ + register w10_t wadj; + LRHSET(wadj, H10MASK, va_insect(e)); /* Set up word */ + op10m_add(w, wadj); /* Add negative adj */ + } else + op10m_addi(w, va_insect(e)); /* Can just add immediately */ + } else +#endif + { + /* Local format */ + register h10_t adj = va_insect(e); + RHSET(w, (RHGET(w)+adj)&H10MASK); /* Add offset to RH */ + if ((h = LHGET(w)+adj) & H10SIGN) { /* New count negative? */ + if ((adj & H10SIGN) && (LHGET(w) & H10SIGN)==0) + PCFTRAPSET(PCF_TR2); /* Neg E made cnt pos -> neg */ + } else { /* New count positive */ + if ((adj & H10SIGN)==0 && (LHGET(w) & H10SIGN)) + PCFTRAPSET(PCF_TR2); /* Pos E made cnt neg -> pos */ + } + LHSET(w, (h & H10MASK)); /* Store new count */ + } + ac_set(ac, w); + return PCINC_1; +} + diff --git a/src/inmove.c b/src/inmove.c new file mode 100644 index 0000000..af22fda --- /dev/null +++ b/src/inmove.c @@ -0,0 +1,718 @@ +/* INMOVE.C - Word move instruction routines +*/ +/* $Id: inmove.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: inmove.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#include "klh10.h" +#include "kn10def.h" /* Machine defs */ +#include "kn10ops.h" /* PDP-10 ops */ + +#ifdef RCSID + RCSID(inmove_c,"$Id: inmove.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* See CODING.TXT for guidelines to coding instruction routines. */ + + +/* Full-word data stuff */ + +insdef(i_exch) /* EXCH AC,E */ +{ + register vmptr_t p = vm_modmap(e); /* Check access, get pointer */ + register w10_t w; + w = vm_pget(p); /* Save mem in temporary loc */ + vm_pset(p, ac_get(ac)); /* Move c(AC) to E */ + ac_set(ac, w); /* Move c(E) to AC */ + return PCINC_1; +} + + +/* MOVEx group - MOVE, MOVEI, MOVEM, MOVES */ + +insdef(i_move) /* MOVE AC,E */ +{ + ac_set(ac, vm_read(e)); + return PCINC_1; +} + +insdef(i_movei) /* MOVEI AC,E */ +{ + ac_setlrh(ac, 0, va_insect(e)); + return PCINC_1; +} + +insdef(i_movem) /* MOVEM AC,E */ +{ + vm_write(e, ac_get(ac)); + return PCINC_1; +} + +insdef(i_moves) /* MOVES AC,E */ +{ + register vmptr_t p = vm_modmap(e); /* Make write access ref */ + if (ac) ac_set(ac, vm_pget(p)); + return PCINC_1; +} + + +/* MOVSx group - MOVS, MOVSI, MOVSM, MOVSS */ + +insdef(i_movs) /* MOVS AC,E */ +{ + register w10_t w; + w = vm_read(e); + ac_setlrh(ac, RHGET(w), LHGET(w)); + return PCINC_1; +} + +insdef(i_movsi) /* MOVSI AC,E */ +{ + ac_setlrh(ac, va_insect(e), 0); + return PCINC_1; +} + +insdef(i_movsm) /* MOVSM AC,E */ +{ + register w10_t w; + LRHSET(w, ac_getrh(ac), ac_getlh(ac)); + vm_write(e, w); + return PCINC_1; +} + +insdef(i_movss) /* MOVSS AC,E */ +{ + register vmptr_t p = vm_modmap(e); /* Make R/W access ref */ + register w10_t w; + LRHSET(w, vm_pgetrh(p), vm_pgetlh(p)); + vm_pset(p, w); /* Store back in mem */ + if (ac) ac_set(ac, w); + return PCINC_1; +} + + +/* MOVNx group - MOVN, MOVNI, MOVNM, MOVNS */ + +insdef(i_movn) /* MOVN AC,E */ +{ + register w10_t w; + w = vm_read(e); + op10mf_movn(w); /* Negate, setting flags if necessary */ + ac_set(ac, w); + return PCINC_1; +} + +insdef(i_movni) /* MOVNI AC,E */ +{ + register w10_t w; + LRHSET(w, 0, va_insect(e)); + op10mf_movn(w); /* Negate, setting flags if 0 */ + ac_set(ac, w); + return PCINC_1; +} + +insdef(i_movnm) /* MOVNM AC,E */ +{ + register vmptr_t p = vm_wrtmap(e); /* Check mem ref first */ + register w10_t w; + w = ac_get(ac); + op10mf_movn(w); /* Negate, setting flags if necessary */ + vm_pset(p, w); + return PCINC_1; +} + +insdef(i_movns) /* MOVNS AC,E */ +{ + register vmptr_t p = vm_modmap(e); /* Check mem ref first */ + register w10_t w; + w = vm_pget(p); + op10mf_movn(w); /* Negate, setting flags if necessary */ + vm_pset(p, w); + if (ac) ac_set(ac, w); + return PCINC_1; +} + + +/* MOVMx group - MOVM, MOVMI, MOVMM, MOVMS */ + +insdef(i_movm) /* MOVM AC,E */ +{ + register w10_t w; + w = vm_read(e); + op10mf_movm(w); /* Make pos, setting flags if necessary */ + ac_set(ac, w); + return PCINC_1; +} + +#if 0 +insdef(i_movmi) /* MOVMI AC,E is identical to MOVEI AC,E */ +#endif + +insdef(i_movmm) /* MOVMM AC,E */ +{ + register vmptr_t p = vm_wrtmap(e); /* Check mem ref first */ + register w10_t w; + w = ac_get(ac); + op10mf_movm(w); /* Make pos, setting flags if necessary */ + vm_pset(p, w); + return PCINC_1; +} + +insdef(i_movms) /* MOVMS AC,E */ +{ + register vmptr_t p = vm_modmap(e); /* Check mem ref first */ + register w10_t w; + w = vm_pget(p); + op10mf_movm(w); /* Make pos, setting flags if necessary */ + vm_pset(p, w); + if (ac) ac_set(ac, w); + return PCINC_1; +} + +/* Double-word data move instructions. +** DMOVE and DMOVEM are handled differently from the other instructions +** that manipulate double words, for greater efficiency. +** +** There is a large fuzzy gray area surrounding the question of page fails +** for double-word operands. In general the KLH10 tries not to do anything +** at all if a page fail would happen on either the first or second word, +** thus avoiding the issue of intermediate results. +** +** BUT! Examination of the ucode indicates otherwise: +** KL DMOVE/DMOVN: Atomic; both words are fetched before any ACs are set. +** A pagefail on read of 2nd word leaves no ACs changed. +** KL DMOVEM/DMOVNM: First gets both ACs, then stores. +** 1st (high) word is stored, then 2nd (low); +** A pagefail on write of 2nd word leaves 1st already stored. +** +** KS DMOVE/DMOVN: same as KL. +** KS DMOVEM/DMOVNM: 2nd word is stored, then 1st!? +** +** The KL diag (DFKEA) agrees with the KL for DMOVE and DMOVEM (DMOVN/M is +** not tested). +** +** Given this discrepancy, the KLH10's approach isn't so bad. But just to +** be ultra safe, I'll attempt to emulate the KS/KL lossage. +*/ + +/* DMOVE checks to see if the double can be copied directly into the +** ACs. If not, intermediate storage must be used to allow for the +** possibility of a page fault between words, or AC/address wraparound, or +** AC overlap if mem ref is to ACs. +*/ + +insdef(i_dmove) /* DMOVE AC,E */ +{ + /* See if fast move OK, normally true. Hope the testing doesn't end up + ** using more time than the conservative case! + ** NOTE this assumes C structure copies are done sequentially. If this + ** is not true, must test for e == ac+1 instead. + */ + if (ac_issafedouble(ac) /* ACs contiguous? */ + && vm_issafedouble(e) /* Mem locs safe too? */ + && (va_insect(e) != ac_off(ac,-1))) /* no AC overlap with Mem? */ + *ac_mapd(ac) = vm_pgetd(vm_xrwmap(e,VMF_READ)); /* Yep! */ + else { + register dw10_t d; /* Conservative case, use intermed stg */ + vm_dread(e, d); /* Fetch the double */ + ac_dset(ac, d); /* Set in ACs */ + } + return PCINC_1; +} + +insdef(i_dmovem) /* DMOVEM AC,E */ +{ + /* See if fast move OK, normally true. Hope the testing doesn't end up + ** using more time than the conservative case! + ** NOTE this assumes C structure copies are done sequentially. If this + ** is not true, must test for e == ac-1 instead. + */ + if (ac_issafedouble(ac) /* ACs contiguous? */ + && vm_issafedouble(e) /* Mem locs safe too? */ + && (va_insect(e) != ac_off(ac,1))) /* no AC overlap with Mem? */ + vm_psetd(vm_xrwmap(e,VMF_WRITE), *ac_mapd(ac)); /* Yep! */ + else { + register dw10_t d; /* Conservative case, use intermed stg */ +#if KLH10_CPU_KL + ac_dget(ac, d); /* Get from ACs */ + vm_write(e, d.w[0]); /* Do first word */ + va_inc(e); + vm_write(e, d.w[1]); /* Do second word */ + +#elif KLH10_CPU_KS + register vaddr_t va; + ac_dget(ac, d); /* Get from ACs */ + va = e; + va_inc(va); + vm_write(va, d.w[1]); /* Do second word first! */ + vm_write(e, d.w[0]); /* Do first word last! */ + +#else /* What I'd really prefer to use - atomic double write */ + register vmptr_t p0, p1; + p0 = vm_xrwmap(e, VMF_WRITE); /* Check access for 1st word */ + va_inc(e); /* Bump E by 1, may wrap funnily */ + p1 = vm_xrwmap(e, VMF_WRITE); /* Check access for 2nd word */ + ac_dget(ac, d); /* Get from ACs */ + vm_pset(p0, d.w[0]); /* Set 1st from AC */ + vm_pset(p1, d.w[1]); /* Set 2nd from AC+1 */ +#endif + } + return PCINC_1; +} + +/* DMOVN is a vanilla double-precision arithmetic instruction. +** May set flags. +*/ +insdef(i_dmovn) /* D_MOVN AC,E */ +{ + register dw10_t d; + vm_dread(e, d); /* Fetch into D */ + op10mf_dmovn(d); /* Negate in place, with flags */ + ac_dset(ac, d); /* Store in ACs */ + return PCINC_1; +} + +/* DMOVNM requires special-casing due to differing order of operation +** for the double-word memory store. +** May set flags. Note flags are set prior to possible page fail, +** thus potentially losing just like the ucode. +*/ +insdef(i_dmovnm) /* DMOVNM AC,E */ +{ + register dw10_t d; + + ac_dget(ac, d); /* Fetch AC, AC+1 into D */ + op10mf_dmovn(d); /* Negate in place, with flags! */ + if (vm_issafedouble(e)) /* Fast move OK? */ + vm_psetd(vm_xrwmap(e,VMF_WRITE), d); /* Yep! */ + else { +#if KLH10_CPU_KL + vm_write(e, HIGET(d)); /* Do first word */ + va_inc(e); + vm_write(e, LOGET(d)); /* Do second word */ + +#elif KLH10_CPU_KS + register vaddr_t va; + va = e; + va_inc(va); + vm_write(va, LOGET(d)); /* Do second word first! */ + vm_write(e, HIGET(d)); /* Do first word last! */ + +#else /* What I'd really prefer to use - atomic double write */ + register vmptr_t p0, p1; + p0 = vm_xrwmap(e, VMF_WRITE); /* Check access for 1st word */ + va_inc(e); /* Bump E by 1, may wrap funnily */ + p1 = vm_xrwmap(e, VMF_WRITE); /* Check access for 2nd word */ + vm_pset(p0, HIGET(d)); /* Set 1st (high) */ + vm_pset(p1, LOGET(d)); /* Set 2nd (low) */ +#endif + } + return PCINC_1; +} + +/* BLT. (XBLT is handled by the EXTEND instr code) */ + +/* BLT - with optimization for case where dest is source+1. +** Note test for using same mapping, since BLT can be used for +** transfers between user and exec maps! +*/ + +/* Note special page map context references for BLT. +** These are so PXCT can work by twiddling the context pointers. +** For: +** Computing E - normal ea_calc() using XEA map +** Store word to destination - normal XRW map +** Read word from source - special XBRW map +*/ +#define vm_srcread(a) (vm_pget(vm_xbrwmap((a), VMF_READ))) + +/* Additional PXCT note for KLX: +** Uhler claims both source and dest must use the same (previous) +** context (thus the only valid AC values are 5 and 15 -- bits 10 and 12 +** must always be set!) +** It isn't clear whether a KLX is expected to ignore those bits +** and always use previous context, or if it simply does undefined things +** if those bits are off (requesting current context). +** For simplicity this code does implement current context mapping +** even on a KLX, which corresponds to the latter option. As long as +** the monitor only uses valid AC bit combinations this is okay. +*/ + +/* There are some ambiguities in the DEC PRM p.2-8 description of BLT. + +* Do the KI/KA test for RH[AC] >= RH(E) just as the KS/KL do, or can + the destination block wrap around? + [Assume yes, no dest wraparound] +* What happens if AC *is* in the destination block? + [Assume value indeterminate unless last word of xfer, then is + that value.] +* What happens if AC is in the source block? + [Assume value indeterminate, but if no pager/int, is original value] + [WRONG!!!! Turns out that for KS/KL, the AC is updated immediately + with the final src,,cnt value! So any copy involving that AC as + source will contain the "final" value. Aborts due to pagefail + or PI will re-clobber it with the correct intermediate value. + Exhibited by this test: MOVE 1,[1,,1] ? BLT 1,1 + which leaves 2,,2 in AC1.] + +* Exactly what is left in the AC for the KL case of RH[AC] > RH(E)? + The PRM says as if the "reverse xfer" happened, but does this + mean the last locs that wd have been referenced, or the next ones? + [Assume latter - the "next" ones.] +* The footnote on PRM p.2-8 claims an extended KL will count from section + 0 up into 1. Exactly what does this mean? That a source block + could consist of stuff from sect 0 mem, then the ACs (cuz 1,,0-17 is + used to ref the ACs), then sect 1 mem? What about the optimized + case where dest == src+1, would this still use the original + word value or would the BLT start reading from the sect 1 source? + [Assume we don't need to emulate this lossage. Yeech.] + +* !! Note from Uhler document that source and dest are incremented in-section + only, even if addresses are global, without altering local/global flag + that pager uses. +*/ + +insdef(i_blt) +{ + register int32 cnt; /* Note signed */ + register vaddr_t src, dst; + register vmptr_t vp; + +#if KLH10_EXTADR + src = dst = e; /* E sets default section & l/g flag */ + va_setinsect(src, ac_getlh(ac)); /* Set up source addr */ + va_setinsect(dst, ac_getrh(ac)); /* Set up dest addr */ +#else + va_lmake(src, 0, ac_getlh(ac)); + va_lmake(dst, 0, ac_getrh(ac)); +#endif + cnt = (va_insect(e) - va_insect(dst)); /* # words to move, less 1 */ + + if (cnt <= 0) { + /* Special case, either just 1 word or dest block would wrap + ** (So-called "reverse transfer" attempt). Main reason for + ** handling this specially is to emulate KL rev-xfer weirdness. + */ + register vmptr_t rp; + + /* First ensure we can carry out the single xfer expected */ + if (!(rp = vm_xbrwmap(src, VMF_READ|VMF_NOTRAP)) + || !(vp = vm_xrwmap(dst, VMF_WRITE|VMF_NOTRAP))) { + pag_fail(); /* Ugh, take page-fail trap */ + } + + /* OK, now if KL or KS, pre-clobber AC appropriately *before* + ** the copy, so that if AC is involved, behavior will emulate + ** the machines. Sigh. + */ +#if KLH10_CPU_KL + /* KL always updates as if the full reverse xfer happened */ + ac_setlrh(ac, (va_insect(src)+cnt+1) & H10MASK, + (va_insect(dst)+cnt+1) & H10MASK); +#elif KLH10_CPU_KS + /* KS only reflects what actually got xfered (according to PRM; + ** it may in fact behave like the KL for all I know!) + */ + ac_setlrh(ac, (va_insect(src)+1) & H10MASK, + (va_insect(dst)+1) & H10MASK); +#endif + vm_pset(vp, vm_pget(rp)); /* Copy directly from source */ + + return PCINC_1; + } + +#if KLH10_CPU_KS || KLH10_CPU_KL + /* Before starting xfer, clobber AC to final value. This emulates + ** the peculiar way the KS/KL do things. Note that due to this + ** pre-clobberage, there is no need to store a final value at the + ** end of a successful BLT. Any aborts MUST TAKE CARE to store the proper + ** intermediate value!! + */ + ac_setlrh(ac, (va_insect(src)+cnt+1) & H10MASK, + (va_insect(dst)+cnt+1) & H10MASK); +#endif /* KS || KL */ + + if ((va_insect(src)+1) == va_insect(dst) /* Special case? */ + && (cpu.vmap.xrw == cpu.vmap.xbrw)) { /* Must have same mapping! */ + + /* Simple set-memory-to-value BLT */ + register w10_t w; + + /* Get first word and remember that. Just in case it helps the + ** compiler optimize, use xrw map instead of xbrw since we only + ** come here if they're the same. + */ + if (!(vp = vm_xrwmap(src, VMF_READ|VMF_NOTRAP))) { + ac_setlrh(ac, va_insect(src), /* Oops, save AC */ + va_insect(dst)); + pag_fail(); /* Take page-fail trap */ + } + w = vm_pget(vp); /* Get word, remember it */ + + do { + CLOCKPOLL(); /* Keep clock going */ + if (INSBRKTEST()) { /* Watch for interrupt */ + ac_setlrh(ac, va_insect(src), /* Oops, save AC */ + va_insect(dst)); + apr_int(); /* Take interrupt */ + } + if (!(vp = vm_xrwmap(dst, VMF_WRITE|VMF_NOTRAP))) { + ac_setlrh(ac, va_insect(src), /* Oops, save AC */ + va_insect(dst)); + pag_fail(); /* Take page-fail trap */ + } + vm_pset(vp, w); /* Store word value */ + va_linc(src); /* Do LOCAL increment of addrs! */ + va_linc(dst); + } while (--cnt >= 0); + } + else do { + + /* Normal BLT transfer */ + register vmptr_t rp; + CLOCKPOLL(); /* Keep clock going */ + if (INSBRKTEST()) { /* Watch for interrupt */ + ac_setlrh(ac, va_insect(src), /* Oops, save AC */ + va_insect(dst)); + apr_int(); /* Take interrupt */ + } + + if (!(rp = vm_xbrwmap(src, VMF_READ|VMF_NOTRAP)) + || !(vp = vm_xrwmap(dst, VMF_WRITE|VMF_NOTRAP))) { + ac_setlrh(ac, va_insect(src), /* Oops, save AC */ + va_insect(dst)); + pag_fail(); /* Take page-fail trap */ + } + vm_pset(vp, vm_pget(rp)); /* Copy directly from source */ + va_linc(src); /* Add 1 to both addrs */ + va_linc(dst); /* Again, LOCAL increment only */ + + } while (--cnt >= 0); + +#if 0 /* No longer needed, code done inline to avoid this check */ + /* Broke out of loop, see if page-failed or not */ + if (cnt >= 0) { /* Use this as indicator */ + ac_setlrh(ac, va_insect(src), /* Oops, save AC */ + va_insect(dst)); + pag_fail(); /* Ugh, take page-fail trap */ + } +#endif + +#if 0 /* KLH10_CPU_KS || KLH10_CPU_KL */ + /* This is no longer used. Instead, to more accurately emulate the + ** KS/KL, the final AC value is stored immediately at the start of + ** the BLT code, so if nothing goes wrong, nothing needs to be done. + */ + + /* End of BLT, but KS/KL must leave AC pointing + ** to what would be the next transfer... unless AC happens + ** to be the last destination! + ** + ** vp still points to last destination, so we can use that for testing, + ** avoiding hassle of determining whether dst is an ac reference! + */ + if (ac_map(ac) != vp) + ac_setlrh(ac, va_insect(src), va_insect(dst)); +#endif /* KS || KL */ + + return PCINC_1; +} + +#if 0 /* Old version, saved in memory of simpler times */ + +insdef(i_blt) +{ + register w10_t w, val; + register int optim; + + e &= H10MASK; + w = ac_get(ac); /* Get src,,dst */ + + if ((LHGET(w)+1)&H10MASK == RHGET(w)) /* Special case? */ + && (cpu.vmap.xrw == cpu.vmap.xbrw)) { /* Must be same mapping! */ + val = vm_srcread(LHGET(w)); + optim = TRUE; + } else optim = FALSE; + for (;;) { + vm_write(RHGET(w), (optim ? val : vm_srcread(LHGET(w)))); + if (RHGET(w) >= e) { /* Last word? Note also stop if AC.rh > E */ + break; /* Win! */ + } + LHSET(w, (LHGET(w)+1)&H10MASK); /* Add 1 to both halves */ + RHSET(w, (RHGET(w)+1)&H10MASK); + ac_set(ac, w); /* Store back in AC in case of pagefail */ + + CLOCKPOLL(); /* Keep clock going */ + if (INSBRKTEST()) apr_int(); /* Watch for interrupt */ + } + + /* End of BLT, but to emulate KS10 exactly must leave AC pointing + ** to what would be the next transfer... unless AC is last destination. + */ + if (ac != RHGET(w)) { + LHSET(w, (LHGET(w)+1)&H10MASK); /* Add 1 to both halves */ + RHSET(w, (RHGET(w)+1)&H10MASK); + ac_set(ac, w); /* Store back in AC */ + } + return PCINC_1; +} +#endif /* 0 - old version */ + +/* BLTBU and BLTUB +** +** New KS10 instructions, not documented in PRM; behavior here is based +** on description from Alan. These are like BLT but transfer blocks of 8-bit +** bytes, packing or unpacking between "Byte format" and "Unibus format". +** +** "Byte format" is the normal PDP-10 format: +** <4 unused bits> +** +** "Unibus format" is sort of like the screwy PDP-11 byte order: +** <2-bits><2-bits> +** +** It isn't clear whether the <2-bits> fields are meaningful or whether they +** are mapped to and from the 4-bit field in Byte format. +** [Assume not mapped; extra bits are cleared in both directions] +** +** [Alan sez: +** Of course in the case of a device that can put 18 bits of data on the +** Unibus, the extra bits show up where the diagram says "2 BITS". (There is +** a bit in the UBA map that says whether the UBA should expect 18-bit data to +** be written to that page. I have no idea what happens if you set it wrong, +** perhaps it just controls whether the extra bits are passed through or +** written as 0.)] +*/ + + +#if KLH10_CPU_KS + +/* BLTBU - Like BLT but transforms each word from Byte to Unibus format. +** BLTUB - Ditto, Unibus to Byte format. +** For more explanation of the code, see BLT. +*/ +#define defblt(name) \ + insdef(name) { register w10_t w, val; \ + register vaddr_t src, dst; \ + register uint18 stop = va_insect(e); \ + w = ac_get(ac); \ + va_lmake(src, 0, LHGET(w)); \ + va_lmake(dst, 0, RHGET(w)); \ + for (;;) { \ + val = vm_srcread(src); \ + blttransform(val); \ + vm_write(dst, val); \ + if (RHGET(w) >= stop) \ + break; \ + LHSET(w, (LHGET(w)+1)&H10MASK); \ + RHSET(w, (RHGET(w)+1)&H10MASK); \ + ac_set(ac, w); \ + CLOCKPOLL(); \ + if (INSBRKTEST()) apr_int(); \ + va_inc(src); \ + va_inc(dst); \ + } \ + if (ac != RHGET(w)) { \ + LHSET(w, (LHGET(w)+1)&H10MASK); \ + RHSET(w, (RHGET(w)+1)&H10MASK); \ + ac_set(ac, w); \ + } \ + return PCINC_1; \ + } + +/* Byte fmt to Unibus fmt, clears extra bits */ +#define blttransform(v) \ + RHSET(v, ((RHGET(v)<<4)&(0377<<8)) \ + | ((LHGET(v)&03)<<6) | ((RHGET(v)>>12)&077) ); \ + LHSET(v, ((LHGET(v)>>10)&0377) | ((LHGET(v)<<6)&(0377<<8)) ) +defblt(i_bltbu) +#undef blttransform + + +/* Unibus fmt to Byte fmt, clears extra bits */ +#define blttransform(v) \ + LHSET(v, ((LHGET(v)&0377)<<10) \ + | ((LHGET(v)&(0377<<8))>>6) \ + | ((RHGET(v)>>6)&03) ); \ + RHSET(v, ((RHGET(v)>>4)&(0377<<4)) \ + | ((RHGET(v)&&077)<<12) ) +defblt(i_bltub) +#undef blttransform + +#endif /* KS */ + +#if KLH10_EXTADR + +/* XMOVEI and XHLLI. +** These two instructions are not as straightforward as they +** might seem. One might expect them to simply: +** (1) use whatever the section number of the EA is, or +** (2) always set LH = 1 if reference is to an AC. +** +** In fact, they do neither. +** The use of LH=1 to represent a global AC address happens ONLY if: +** (a) EA is local, (b) section # is non-zero, and (c) in-section +** address is 0-17 inclusive. +** +** In particular, a local reference to 0,,AC (even if made while PC_ISEXT) +** will be stored as 0,,AC and not 1,,AC. +** Likewise, a global reference to 0,,AC is stored as 0,,AC even though +** it *DOES* reference the ACs, just like 1,,AC! +** +** The following table sums it up: +** +** LOCAL 0,,AC => 0,,AC +** LOCAL NZ,,AC => 1,,AC +** LOCAL *,,NAC => *,,NAC +** GLOBAL *,,* => *,,* +** +** This behavior is coded into the va_iscvtacref() macro. +** +** Finally, this same conversion is also done for the EA stored as a result +** of a LUUO, MUUO, or pagefail trap. +** (what about PC?) +*/ + +/* XMOVEI - Actually an extended SETMI. +** Special boolean variant when using extended addressing +*/ +insdef(i_xmovei) +{ + if (va_iscvtacref(e)) + ac_setlrh(ac, 1, va_insect(e)); /* Global AC reference */ + else + ac_setlrh(ac, va_sect(e), va_insect(e)); + return 1; +} + + +/* XHLLI - Actually an extended HLLI. +** Special halfword variant when using extended addressing +*/ +insdef(i_xhlli) +{ + if (va_iscvtacref(e)) + ac_setlh(ac, 1); /* Global AC reference */ + else + ac_setlh(ac, va_sect(e)); + return 1; +} + +#endif /* KLH10_EXTADR */ diff --git a/src/intest.c b/src/intest.c new file mode 100644 index 0000000..bd62627 --- /dev/null +++ b/src/intest.c @@ -0,0 +1,300 @@ +/* INTEST.C - Logical and Arithmetic test instruction routines +*/ +/* $Id: intest.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: intest.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#include "klh10.h" +#include "kn10def.h" /* Machine defs */ +#include "kn10ops.h" /* PDP-10 ops */ + +#ifdef RCSID + RCSID(intest_c,"$Id: intest.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* See CODING.TXT for guidelines to coding instruction routines. */ + + +/* Logical Testing */ + +#define testinsdef(name, nameE, nameA, nameN) \ + insdef(name) \ + { testop(;); \ + return PCINC_1; \ + } \ + insdef(nameE) \ + { register pcinc_t ret; \ + testop(skipop(==);); \ + return ret; \ + } \ + insdef(nameA) \ + { testop(;); \ + return PCINC_2; \ + } \ + insdef(nameN) \ + { register pcinc_t ret; \ + testop(skipop(!=);); \ + return ret; \ + } + +#define skipop(relop) \ + ret = ((ac_getlh(ac)&va_insect(e)) relop 0 ? PCINC_2 : PCINC_1) +#define testop(stmt) stmt +testinsdef(i_tln, i_tlne, i_tlna, i_tlnn) /* TLN */ +#undef testop + +#define testop(stmt) stmt ac_setlh(ac, ac_getlh(ac) & ~va_insect(e)) +testinsdef(i_tlz, i_tlze, i_tlza, i_tlzn) /* TLZ */ +#undef testop + +#define testop(stmt) stmt ac_setlh(ac, ac_getlh(ac) | va_insect(e)) +testinsdef(i_tlo, i_tloe, i_tloa, i_tlon) /* TLO */ +#undef testop + +#define testop(stmt) stmt ac_setlh(ac, ac_getlh(ac) ^ va_insect(e)) +testinsdef(i_tlc, i_tlce, i_tlca, i_tlcn) /* TLC */ +#undef testop +#undef skipop + +#define skipop(relop) ret = ((ac_getrh(ac)&va_insect(e)) relop 0 ? 2 : 1) +#define testop(stmt) stmt +testinsdef(i_trn, i_trne, i_trna, i_trnn) /* TRN */ +#undef testop + +#define testop(stmt) stmt ac_setrh(ac, ac_getrh(ac) & ~va_insect(e)) +testinsdef(i_trz, i_trze, i_trza, i_trzn) /* TRZ */ +#undef testop + +#define testop(stmt) stmt ac_setrh(ac, ac_getrh(ac) | va_insect(e)) +testinsdef(i_tro, i_troe, i_troa, i_tron) /* TRO */ +#undef testop + +#define testop(stmt) stmt ac_setrh(ac, ac_getrh(ac) ^ va_insect(e)) +testinsdef(i_trc, i_trce, i_trca, i_trcn) /* TRC */ +#undef testop +#undef skipop + +/* TDxx instructions need fullword data operations */ + +#define skipop(relop) ret = (op10m_tdnn(a, w) relop 0 ? 2 : 1) +#define testop(stmt) register w10_t a, w; \ + w = vm_read(e); a = ac_get(ac); stmt +testinsdef(i_tdn, i_tdne, i_tdna, i_tdnn) /* TDN */ +#undef testop + +#define testop(stmt) register w10_t a, w; \ + w = vm_read(e); a = ac_get(ac); stmt \ + op10m_andcm(a, w); ac_set(ac, a); +testinsdef(i_tdz, i_tdze, i_tdza, i_tdzn) /* TDZ */ +#undef testop + +#define testop(stmt) register w10_t a, w; \ + w = vm_read(e); a = ac_get(ac); stmt \ + op10m_ior(a, w); ac_set(ac, a); +testinsdef(i_tdo, i_tdoe, i_tdoa, i_tdon) /* TDO */ +#undef testop + +#define testop(stmt) register w10_t a, w; \ + w = vm_read(e); a = ac_get(ac); stmt \ + op10m_xor(a, w); ac_set(ac, a); +testinsdef(i_tdc, i_tdce, i_tdca, i_tdcn) /* TDC */ +#undef testop +/* Preserve skipop for TSxx instrs below */ + +#define testop(stmt) register w10_t a, w, ws; \ + ws = vm_read(e); LRHSET(w, RHGET(ws), LHGET(ws)); \ + a = ac_get(ac); stmt +testinsdef(i_tsn, i_tsne, i_tsna, i_tsnn) /* TSN */ +#undef testop + +#define testop(stmt) register w10_t a, w, ws; \ + ws = vm_read(e); LRHSET(w, RHGET(ws), LHGET(ws)); \ + a = ac_get(ac); stmt \ + op10m_andcm(a, w); ac_set(ac, a); +testinsdef(i_tsz, i_tsze, i_tsza, i_tszn) /* TSZ */ +#undef testop + +#define testop(stmt) register w10_t a, w, ws; \ + ws = vm_read(e); LRHSET(w, RHGET(ws), LHGET(ws)); \ + a = ac_get(ac); stmt \ + op10m_ior(a, w); ac_set(ac, a); +testinsdef(i_tso, i_tsoe, i_tsoa, i_tson) /* TSO */ +#undef testop + +#define testop(stmt) register w10_t a, w, ws; \ + ws = vm_read(e); LRHSET(w, RHGET(ws), LHGET(ws)); \ + a = ac_get(ac); stmt \ + op10m_xor(a, w); ac_set(ac, a); +testinsdef(i_tsc, i_tsce, i_tsca, i_tscn) /* TSC */ +#undef testop +#undef skipop + +#undef testinsdef + +/* Arithmetic testing */ + +#define aobjdef(name, relop) \ + insdef(name) \ + { register w10_t w; w = ac_get(ac); \ + LHSET(w, (LHGET(w)+1) & H10MASK); \ + RHSET(w, (RHGET(w)+1) & H10MASK); \ + ac_set(ac, w); \ + if (op10m_skipl(w) relop 0) { \ + PC_JUMP(e); \ + return PCINC_0; \ + } else return PCINC_1; \ + } + +aobjdef(i_aobjn, !=) /* AOBJN */ +aobjdef(i_aobjp, ==) /* AOBJP */ +#undef aobjdef + +#define caiskip(name, relop, reltest) \ + insdef(name) { register w10_t a; \ + a = ac_get(ac); \ + return (reltest) ? PCINC_2 : PCINC_1; } + +insdef(i_cai) { return PCINC_1; } /* No-op, never skips */ +caiskip(i_cail, < , op10m_cail(a,va_insect(e))) +caiskip(i_caie, ==, op10m_caie(a,va_insect(e))) +caiskip(i_caile, <=, op10m_caile(a,va_insect(e))) +insdef(i_caia) { return PCINC_2; } /* Always skips */ +caiskip(i_caige, >=, op10m_caige(a,va_insect(e))) +caiskip(i_cain, !=, op10m_cain(a,va_insect(e))) +caiskip(i_caig, > , op10m_caig(a,va_insect(e))) +#undef caiskip + +#define camskip(name, relop, reltest) \ + insdef(name) \ + { register w10_t a,b; \ + b = vm_read(e); a = ac_get(ac); \ + return (reltest) ? PCINC_2 : PCINC_1; \ + } + +insdef(i_cam) { (void)vm_read(e); return PCINC_1; } /* No-op, never skips */ +camskip(i_caml, < , op10m_caml(a,b)) +camskip(i_came, ==, op10m_came(a,b)) +camskip(i_camle, <=, op10m_camle(a,b)) +insdef(i_cama) { (void)vm_read(e); return PCINC_2; } /* Always skips */ +camskip(i_camge, >=, op10m_camge(a,b)) +camskip(i_camn, !=, op10m_camn(a,b)) +camskip(i_camg, > , op10m_camg(a,b)) +#undef camskip + + /* Common tests for next 6 groups (JUMP, SKIP, AOS/SOS, AOJ/SOJ) */ +#define skiptestL (op10m_skipl(w)) +#define skiptestE (op10m_skipe(w)) +#define skiptestLE (op10m_skiple(w)) +#define skiptestGE (op10m_skipge(w)) +#define skiptestN (op10m_skipn(w)) +#define skiptestG (op10m_skipg(w)) + +#define testjump(name, relop, ifjump) \ + insdef(name) \ + { register w10_t w; w = ac_get(ac); \ + return ifjump ? (PC_JUMP(e), PCINC_0) : PCINC_1; \ + } + +insdef(i_jump) { return PCINC_1; } /* No-op, never jumps */ +testjump(i_jumpl, < , skiptestL) +testjump(i_jumpe, ==, skiptestE) +testjump(i_jumple, <=, skiptestLE) +insdef(i_jumpa) { return (PC_JUMP(e), PCINC_0); } /* Always jumps */ +testjump(i_jumpge, >=, skiptestGE) +testjump(i_jumpn, !=, skiptestN) +testjump(i_jumpg, > , skiptestG) +#undef testjump + + +#define moveskip(name, ifskip) \ + insdef(name) \ + { register w10_t w; w = vm_read(e); \ + if (ac) ac_set(ac, w); \ + return ifskip ? PCINC_2 : PCINC_1; \ + } +moveskip(i_skip, 0) /* SKIP - Never skips */ +moveskip(i_skipl, skiptestL) +moveskip(i_skipe, skiptestE) +moveskip(i_skiple,skiptestLE) +moveskip(i_skipa, 1) /* SKIPA - Always skips */ +moveskip(i_skipge,skiptestGE) +moveskip(i_skipn, skiptestN) +moveskip(i_skipg, skiptestG) +#undef moveskip + +#define bumpskip(name, bumpop, ifskip) \ + insdef(name) \ + { register vmptr_t p = vm_modmap(e); \ + register w10_t w; w = vm_pget(p); \ + bumpop; \ + vm_pset(p, w); \ + if (ac) ac_set(ac, w); \ + return ifskip ? PCINC_2 : PCINC_1; \ + } +bumpskip(i_aos, op10mf_inc(w), 0) /* AOS - never skips */ +bumpskip(i_aosl, op10mf_inc(w), skiptestL) +bumpskip(i_aose, op10mf_inc(w), skiptestE) +bumpskip(i_aosle,op10mf_inc(w), skiptestLE) +bumpskip(i_aosa, op10mf_inc(w), 1) /* AOSA - Always skips */ +bumpskip(i_aosge,op10mf_inc(w), skiptestGE) +bumpskip(i_aosn, op10mf_inc(w), skiptestN) +bumpskip(i_aosg, op10mf_inc(w), skiptestG) + +bumpskip(i_sos, op10mf_dec(w), 0) /* SOS - never skips */ +bumpskip(i_sosl, op10mf_dec(w), skiptestL) +bumpskip(i_sose, op10mf_dec(w), skiptestE) +bumpskip(i_sosle,op10mf_dec(w), skiptestLE) +bumpskip(i_sosa, op10mf_dec(w), 1) /* SOSA - Always skips */ +bumpskip(i_sosge,op10mf_dec(w), skiptestGE) +bumpskip(i_sosn, op10mf_dec(w), skiptestN) +bumpskip(i_sosg, op10mf_dec(w), skiptestG) +#undef bumpskip + + +#define bumpjump(name, bumpop, ifskip) \ + insdef(name) \ + { register acptr_t p = ac_map(ac); \ + register w10_t w; w = ac_pget(p); \ + bumpop; \ + ac_pset(p, w); \ + return ifskip ? (PC_JUMP(e), PCINC_0) : PCINC_1; \ + } +bumpjump(i_aoj, op10mf_inc(w), 0) /* AOJ - never jumps */ +bumpjump(i_aojl, op10mf_inc(w), skiptestL) +bumpjump(i_aoje, op10mf_inc(w), skiptestE) +bumpjump(i_aojle,op10mf_inc(w), skiptestLE) +bumpjump(i_aoja, op10mf_inc(w), 1) /* AOJA - Always jumps */ +bumpjump(i_aojge,op10mf_inc(w), skiptestGE) +bumpjump(i_aojn, op10mf_inc(w), skiptestN) +bumpjump(i_aojg, op10mf_inc(w), skiptestG) + +bumpjump(i_soj, op10mf_dec(w), 0) /* SOJ - never jumps */ +bumpjump(i_sojl, op10mf_dec(w), skiptestL) +bumpjump(i_soje, op10mf_dec(w), skiptestE) +bumpjump(i_sojle,op10mf_dec(w), skiptestLE) +bumpjump(i_soja, op10mf_dec(w), 1) /* SOJA - Always jumps */ +bumpjump(i_sojge,op10mf_dec(w), skiptestGE) +bumpjump(i_sojn, op10mf_dec(w), skiptestN) +bumpjump(i_sojg, op10mf_dec(w), skiptestG) +#undef bumpjump + +#undef skiptest + diff --git a/src/klh10.c b/src/klh10.c new file mode 100644 index 0000000..9cf960b --- /dev/null +++ b/src/klh10.c @@ -0,0 +1,2720 @@ +/* KLH10.C - Main for KLH10 (also Front End Console for now) +*/ +/* $Id: klh10.c,v 2.4 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: klh10.c,v $ + * Revision 2.4 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#include +#include /* Malloc and friends */ +#include +#include +#include +#include +#include +#include /* For error-reporting functions */ + +#include "klh10.h" +#include "kn10mac.h" /* FLD macros */ +#include "kn10def.h" +#include "kn10dev.h" +#include "kn10ops.h" +#include "wfio.h" +#include "feload.h" +#include "prmstr.h" +#include "dvcty.h" /* For cty_ functions */ + +#if KLH10_CPU_KS +# include "dvuba.h" /* So can get at device info */ +#endif + +#ifdef RCSID + RCSID(klh10_c,"$Id: klh10.c,v 2.4 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Exported functions */ +void klh10_main(int argc, char **argv); +void fe_shutdown(void); +void fe_traceprint(w10_t, vaddr_t); +void fe_begpcfdbg(FILE *f); +void fe_endpcfdbg(FILE *f); +void pishow(FILE *); +void pcfshow(FILE *, h10_t); +void insprint(FILE *, int); +/* void panic(char *, ...); */ /* Declared in kn10def.h */ + +/* Imported functions */ +extern void apr_init(void); +extern int apr_run(void); + +/* Local function kept external for easier debug access */ +void errpt(void); + + +/* Local variables */ + +static char licwarn[] ="\n\ +===============\n\ +This is proprietary and confidential code of Ken Harrenstien\n\ +and may only be used under the terms of the license agreement.\n\ +!! DECOMPILATION OF THIS SOFTWARE VIOLATES THE LICENSE AGREEMENT !!\n\ +===============\n"; + +int proc_bkgd = FALSE; /* TRUE if want to run in background */ +int fedevchkf = FALSE; /* TRUE to do periodic device attention checks */ +ospri_t proc_pri = 0; /* CPU process priority on host OS */ +char *cmdprompt = /* Set initial command prompt */ +#ifdef KLH10_CMDPROMPT + KLH10_CMDPROMPT; +#else + "KLH10> "; +#endif + +char *ld_fmt = NULL; /* Current load file format */ +char *ld_dfmt = /* Default format if none specified */ +#if KLH10_SYS_ITS + "u36"; /* Alan's Unixified format */ +#else + "c36"; /* Core-Dump format */ +#endif +struct loadinfo ld_inf; /* Args to and results from loader */ + +enum fevmmode { /* FEVM_XMAP memory mapping mode defs */ + FEVM_DFLT, /* Use FE default mode */ + FEVM_CUR, /* Current machine mode mapping */ + FEVM_PHYS, /* Force physical map */ + FEVM_EXEC, /* Force exec map */ + FEVM_USER, /* Force user map */ + FEVM_ACB /* Select AC block */ +}; + +/* DDT mode variables */ +vaddr_t ddt_loadsa; /* Last loaded start address */ +vaddr_t ddt_cloc; /* Current location (.) */ +enum fevmmode ddt_clmode; /* Current location FEVM mode */ +w10_t ddt_val; /* Last value */ + +/* Flags to pinstr() */ +#define PINSTR_OPS 01 /* Show operands of instr */ +#define PINSTR_EA 02 /* Use E furnished */ +void pinstr(FILE *, w10_t, int, vaddr_t); + +/* Local Function predeclarations */ + +void int_fecty(int); +static void klh10_init(void); +static void mem_init(void), mem_term(void); +static int mem_setlock(FILE *, FILE *, int); +static void swinit(int, char **); +static void aprcont(int, vaddr_t); +static int aprhalted(void); +static void wd1print(FILE *, w10_t); +static void wd2print(FILE *, w10_t); +static void addrprint(FILE *, vaddr_t, enum fevmmode); +static void nextinsprint(FILE *, int); +static void easymprint(FILE *, vaddr_t); +static int addrparse(char *, vaddr_t *, enum fevmmode *); +static char *strf6(char **, w10_t); +static struct tm *timefrits(struct tm *, w10_t); + +vmptr_t fevm_xmap(vaddr_t, enum fevmmode); /* FE memory mapping */ + +/****************** Command Tables and dispatch ******************/ + +/* New version of command parser -- succumb to GDB, DBX, UNIX influence. +** +** Old version used an even simpler one-character instant-action parser. +*/ + +static FILE *cminfile = NULL; /* Set if reading commands from file */ +static char *cminfname; /* Filename */ +static int cminchar(void); /* Funct to read from file or TTY */ + +#define CMDQCHAR '\\' /* Quote char for token parsing */ + +#ifndef CMDBUFLEN +# define CMDBUFLEN 512 +#endif +#ifndef CMDMAXARG +# define CMDMAXARG 10 +#endif + +struct cmd_s { + int cmd_flags; /* State flags */ + char *cmd_prm; /* Pointer to command prompt */ + char *cmd_buf; /* Pointer to start of buffer */ + size_t cmd_blen; /* Size of buffer */ + int cmd_left; /* # chars left for current cmd being input */ + char *cmd_inp; /* Input deposit pointer */ + char *cmd_rdp; /* Readout pointer */ + size_t cmd_rleft; /* # chars left to read */ + + /* Provide all command routines with their desired arguments */ + char *cmd_arglin; /* Original pointer to start of args on line */ + int cmd_argc; /* # of tokens */ + char *cmd_argv[CMDMAXARG+1]; /* Array of token pointers */ + char *cmd_tdp; /* Next free loc in token buffer */ + size_t cmd_tleft; /* # chars free in token buffer */ + char cmd_tokbuf[CMDBUFLEN+CMDMAXARG]; + +#if 0 + char *cmd_wbf; /* Pointer to work buffer */ + size_t cmd_wblen; /* Size in chars */ + char *cmd_wbp; /* Current deposit ptr */ + size_t cmd_wbleft; /* # chars left */ +#endif +} command; + +#define CMDF_ACTIVE 01 /* Activation char seen, execute accumulated cmd */ +#define CMDF_INACCUM 02 /* In accumulation phase */ +#define CMDF_NOPRM 040 /* Disable prompt */ + +static char cmdbuf[CMDBUFLEN]; /* Original command string buffer */ +#if 0 +static char cmdwbf[CMDBUFLEN]; /* Working buffer */ +#endif + +struct cmkey_s { + char *cmk_key; + union cmnode *cmk_p; +}; +struct cmrtn_s { + void (*cmr_vect)(struct cmd_s *); /* Function to call */ + int cmr_flgs; /* Misc flags */ + char *cmr_synt; /* Arg syntax */ + char *cmr_help; /* Short one-line help */ + char *cmr_desc; /* Long description */ +}; + +#define CMRF_NOARG 01 /* Command takes no args */ +#define CMRF_TOKS 010 /* Command wants whole line tokenized, via cm */ +#define CMRF_TLIN 020 /* Command wants overall line arg, via cm */ +#define CMRF_CMPTR 040 /* Command wants just cmd state ptr */ + +union cmnode { /* All possible nodes for a keyword */ + struct cmrtn_s cmn_rtn; +}; + + +/* Predeclarations */ +void cmdinit(struct cmd_s *, char *, char *, size_t); +int cmdexec(struct cmd_s *); +int cmdaccum(struct cmd_s *); + +struct cmkey_s *cmdkeylookup(char *, struct cmkey_s *, struct cmkey_s **); +char *cmdlsetup(struct cmd_s *); +static void slinlim(char *); + + +/* CMDDEF is used to define top-level commands. It does not accumulate +** them into a table (C is far too puny for that) but gathers together +** various information that a higher-level table can then point to. +*/ +#define CMDDEF(deflab, func, flgs, argsyn, minihelp, longdesc) \ + static void func(struct cmd_s *); \ + static struct cmrtn_s deflab = { func, flgs, argsyn, minihelp, longdesc }; + +CMDDEF(cd_ques, fc_ques, CMRF_NOARG, NULL, + "How to get help", "") +CMDDEF(cd_help, fc_help, CMRF_TOKS, NULL, + "Basic help", "") +CMDDEF(cd_quit, fc_quit, CMRF_NOARG, NULL, + "Quit emulator", "") +CMDDEF(cd_load, fc_load, CMRF_TOKS, "", + "Load binary into KN10", "") +CMDDEF(cd_dump, fc_dump, CMRF_TOKS, "", + "Dump binary from KN10", "") +CMDDEF(cd_go, fc_go, CMRF_TLIN, "[]", + "Start KN10 at address", "") +CMDDEF(cd_shutdown,fc_shutdown,CMRF_NOARG,NULL, + "Halt OS gracefully", "") +CMDDEF(cd_reset, fc_reset, CMRF_NOARG, NULL, + "Halt & Reset KN10", "") +CMDDEF(cd_exa, fc_exa, CMRF_TLIN, "[]", + "Show word at address", "") +CMDDEF(cd_exnext,fc_exnext, CMRF_NOARG, NULL, + "Show Next word", "") +CMDDEF(cd_exprev,fc_exprev, CMRF_NOARG, NULL, + "Show Previous word", "") +CMDDEF(cd_dep, fc_dep, CMRF_TOKS, " ", + "Deposit value at address", "") +CMDDEF(cd_bkpt, fc_bkpt, CMRF_TLIN, "", + "Set breakpoint at PC loc", "") +CMDDEF(cd_step, fc_step, CMRF_NOARG, NULL, + "Single-Step KN10", "") +CMDDEF(cd_proc, fc_proc, CMRF_TLIN, "<#>", + "Proceed # instrs", "") +CMDDEF(cd_cont, fc_cont, CMRF_NOARG, NULL, + "Continue KN10", "") +CMDDEF(cd_view, fc_view, CMRF_NOARG, NULL, + "View KN10 status", "") +CMDDEF(cd_set, fc_set, CMRF_TLIN, "[ []]", + "Set/show KLH10 variables", "") +CMDDEF(cd_trace, fc_trace, CMRF_NOARG, NULL, + "Toggle execution trace", "") +CMDDEF(cd_halt, fc_halt, CMRF_NOARG, NULL, + "Halt KN10 immediately", "") +CMDDEF(cd_zero, fc_zero, CMRF_NOARG, NULL, + "Zero first 256K memory", "") +CMDDEF(cd_devload,fc_devload, CMRF_TLIN, + " ", + "Load dynamic-library device driver", "") +CMDDEF(cd_devdef,fc_devdef, CMRF_TLIN, + " {ub<#>,} ", + "Define, bind, and initialize device", "") +CMDDEF(cd_devshow,fc_devshow, CMRF_TLIN, + "[]", + "Show device driver & definition binding info", "") +#if KLH10_EVHS_INT +CMDDEF(cd_devevshow,fc_devevshow, CMRF_TLIN, + "[]", + "Show device event registration info", "") +#endif +CMDDEF(cd_dev_cmd,fc_dev_cmd, CMRF_TLIN, + " ", + "Execute device-dependent command", "") + +CMDDEF(cd_devboot,fc_devboot, CMRF_TLIN, + " [halt]", + "Boot from device", "") +CMDDEF(cd_devmnt,fc_devmnt, CMRF_TLIN, + " []", + "Mount device media", "") +CMDDEF(cd_devunmnt,fc_devunmnt, CMRF_TLIN, + "", + "Unmount device media", "") +CMDDEF(cd_devdbg,fc_devdbg, CMRF_TLIN, + " []", + "Set device debug value (0=none)", "") +CMDDEF(cd_devwait,fc_devwait, CMRF_TLIN, + "[] []", + "Wait for device (or all devs)", "") + + +#define KEYSBEGIN(name) struct cmkey_s name[] = { +#define KEYDEF(key,nod) { key, (union cmnode *)(&nod) }, +#define KEYSEND { 0, 0 } }; + +KEYSBEGIN(fectbkeys) + KEYDEF("?", cd_ques) + KEYDEF("help", cd_help) + KEYDEF("quit", cd_quit) + KEYDEF("load", cd_load) + KEYDEF("dump", cd_dump) + KEYDEF("go", cd_go) + KEYDEF("shutdown", cd_shutdown) + KEYDEF("reset", cd_reset) + KEYDEF("examine", cd_exa) + KEYDEF("next-examine", cd_exnext) + KEYDEF("^-examine", cd_exprev) + KEYDEF("deposit", cd_dep) + KEYDEF("breakpt", cd_bkpt) + KEYDEF("1-step", cd_step) + KEYDEF("proceed", cd_proc) + KEYDEF("continue", cd_cont) + KEYDEF("view", cd_view) + KEYDEF("set", cd_set) + KEYDEF("trace-toggle", cd_trace) + KEYDEF("halt", cd_halt) + KEYDEF("zero", cd_zero) + KEYDEF("devdefine", cd_devdef) + KEYDEF("devdebug", cd_devdbg) + KEYDEF("devboot", cd_devboot) + KEYDEF("devmount", cd_devmnt) + KEYDEF("devunmount",cd_devunmnt) + KEYDEF("devwait", cd_devwait) + KEYDEF("devshow", cd_devshow) +#if KLH10_EVHS_INT + KEYDEF("devevshow", cd_devevshow) +#endif + KEYDEF("dev", cd_dev_cmd) + KEYDEF("devload", cd_devload) +KEYSEND + +static void error(char *, ...), syserr(int, char *, ...); + +jmp_buf errenv; + +int initdone = 0; + +void +errpt(void) /* Call this to restart loop */ +{ + if (!initdone) { + fprintf(stderr, "Died during startup... goodbye!\n"); + os_exit(1); + } + + /* Return to main KLH10 command processor loop. + ** All implementations of longjmp() had better know how to jump out of + ** a signal handler context! + */ + longjmp(errenv, 1); +} + +/* Print out KLH10 compile-time configuration info + */ +#include "klh10s.h" /* Define string equivs to config params! */ + +static void +pconfig(FILE *f) +{ + fprintf(f, "Compiled for %s on %s with word model %s\n", + KLH10S_CENV_SYS_, KLH10S_CENV_CPU_, WORD10_MODEL); + fputs("Emulated config:\n", f); + fprintf(f, "\t CPU: %s SYS: %s Pager: %s APRID: %ld\n", + KLH10S_CPU_, KLH10S_SYS_, KLH10S_PAG_, + (long)KLH10_APRID_SERIALNO); + fprintf(f, "\t Memory: %ld pages of %d words (%s)\n", + (long)PAG_MAXPHYSPGS, (int)PAG_SIZE, + (KLH10_MEM_SHARED ? "SHARED" : "private")); + fprintf(f, "\t Time interval: %s Base: %s", + KLH10S_ITIME_, KLH10S_RTIME_); +#if KLH10_SYS_ITS + fprintf(f, " Quantums: %s", KLH10S_QTIME_); +#endif +#if KLH10_CLK_ITHZFIX + fprintf(f, "\n\t Interval default: %dHz\n", KLH10_CLK_ITHZFIX); +#else + fprintf(f, "\n\t Interval default: set by 10\n"); +#endif + fprintf(f, "\t Internal clock: %s\n", KLH10S_CLKTRG_); + + /* Show hardware emulation stuff first, then software features */ + fprintf(f, "\t Other:%s\n", + KLH10S_MCA25 + KLH10S_JPC + KLH10S_DEBUG + KLH10S_PCCACHE + KLH10S_CTYIO_INT + KLH10S_IMPIO_INT + KLH10S_EVHS_INT + ); + + /* Show peripheral device drivers known at compile time */ + fprintf(f, "\t Devices:%s\n", + KLH10S_DEV_DTE + KLH10S_DEV_RH + KLH10S_DEV_RPXX + KLH10S_DEV_TM03 + KLH10S_DEV_NI20 + KLH10S_DEV_DZ11 + KLH10S_DEV_CH11 + KLH10S_DEV_LHDH + ); +} + +static void +pversion(FILE *f) +{ + fputs("KLH10", f); +#ifdef KLH10_VERSION + fprintf(f, " %s", KLH10_VERSION); +#endif +#ifdef KLH10_CLIENT + fprintf(f, " (%s)", KLH10_CLIENT); +#endif +#if defined(__DATE__) && defined(__TIME__) + fprintf(f, " built %s %s", __DATE__, __TIME__); +#endif + fputc('\n', f); +} + +static void +pgreeting(FILE *f) +{ + pversion(f); +#ifdef KLH10_COPYRIGHT + fprintf(f, "%s\n", KLH10_COPYRIGHT); +#endif +#ifdef KLH10_WARRANTY + fprintf(f, "%s\n", KLH10_WARRANTY); +#endif + fputc('\n', f); + pconfig(f); +} + + +void +klh10_main(int argc, + char **argv) +{ + /* Handle command line args/switches, if any */ + swinit(argc, argv); + + os_init(); /* Initialize any OS-dependent stuff */ + + /* Set up some TTY stuff. Ensure stdout is unbuffered, like stderr, + ** to avoid the otherwise confusing skews between the two streams. + */ + setbuf(stdout, (char *)NULL); + if (proc_bkgd) { + fprintf(stderr, "[Running in background]\n"); + os_ttybkgd(); /* Hack for now - use this to inform OS code */ + } + + pgreeting(stdout); /* Print greeting message if one */ + klh10_init(); /* Do machine init and configuring */ + + if (!setjmp(errenv)) { + /* Do once-only first-time stuff that might interrupt */ + initdone = TRUE; /* errenv jmpbuf now set */ + os_ttyinit(int_fecty); /* Initialize our terminal, provide int hook */ + + } else { + /* Recover from error catch - something called errpt(). */ + printf(""); + /* If reading from command file, abort it */ + if (cminfile) { + fclose(cminfile); + cminfile = NULL; + printf("[Aborted input from \"%s\"]\n", cminfname); + } + } + os_ttyreset(); /* Reset our terminal mode stuff */ + + /* Enter command parser loop */ + printf("\n"); /* Make sure we prompt on a new line */ + cmdinit(&command, cmdprompt, cmdbuf, sizeof(cmdbuf)); + for (;;) { /* Loop here... */ + if (!cmdlsetup(&command)) { /* Read typein command line */ + printf("\n"); /* If failed, try again */ + continue; + } + if (fedevchkf) /* If checking for dev attn, */ + fedevchkf = dev_dpchk_ctl(TRUE); /* do so here */ + (void) cmdexec(&command); /* Parse and execute */ + } +} + +void +int_fecty(int sig) +{ + INTF_SET(cpu.intf_fecty); /* Say FE CTY interrupt went off */ + INSBRKSET(); /* Say something happened */ +} + + +void +fc_quit(struct cmd_s *cm) +{ + printf("Are you sure you want to quit? [Confirm]"); + os_ttycmforce(); + switch (cminchar()) { + case '\r': + case '\n': + case 'y': + case 'Y': + case -1: /* EOF */ + break; + default: /* Anything else prevents quit */ + return; + } + printf("Shutting down..."); + + dev_term(); /* Power off all devices that might need it */ + + mem_term(); /* Flush memory in case shared */ + + printf("Bye!\n"); + os_exit(0); +} + +/* FE_SHUTDOWN - Attempt to bring down PDP-10 OS and quit emulator as +** gracefully as possible *without* any user interaction. +** +** This is intended to be used when operating as a background process, +** when errors that would normally halt and await input should +** instead attempt to give both the PDP-10 OS and the emulator a +** chance to clean up before the process is killed. This currently +** means: +** (1) Any request for TTY input - in bkgd mode that implies something +** went wrong. +** (2) Receipt of a SIGTERM software termination signal. +** +** Probably better would be a way to suspend operations and then allow +** re-attaching a TTY to the emulator -- but UNIX sucks in that regard +** and still hasn't implemented technology that existed 25 years ago! +*/ + +static int +fe_shuttmo(void *arg) /* arg is ignored */ +{ + printf("[Auto-shutdown timed out]\n"); + fe_shutdown(); /* Re-invoke next phase of shutdown */ + return CLKEVH_RET_KILL; /* Won't actually return */ +} + +void +fe_shutdown(void) +{ + static int shutstate = 0; /* Initial shutdown state */ + + switch (shutstate) { + case 0: + ++shutstate; + /* First determine whether a PDP-10 OS ever actually seemed to get + ** going; a good heuristic is to see if paging mode is on. + ** If so, attempt to trigger a shutdown. + */ + if (cpu.mr_paging) { + /** Attempt OS shutdown! + ** Set a clock timeout of N seconds, after which to force shutdown + ** anyway. For now, set N = 3. + */ + printf("[Attempting auto-shutdown]\n"); + (void) clk_tmrget(fe_shuttmo, (void *)NULL, + (int32) CLK_USECS_PER_SEC * 3); + fc_shutdown((struct cmd_s *)NULL); /* No input required for this */ + /* Will not return */ + } + + case 1: + ++shutstate; + + case 2: + ++shutstate; + printf("[Starting auto-quit]\n"); + dev_term(); /* Power off all devices that might need it */ + /* This should kill all DP subprocs */ + case 3: + ++shutstate; + mem_term(); /* Flush memory in case using shared segs */ + + case 4: + ++shutstate; + printf("[Exiting]\n"); + } + os_exit(1); /* Die with an error */ +} + +/* ERROR - Called only by FE code to report some error in interacting +** with the user. +*/ +static void +error(char *fmt, ...) +{ + fprintf(stderr, "\n"); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fprintf(stderr, "\n"); + + errpt(); +} + + +/* SYSERR - Called only by FE code to report some OS error in interacting +** with the user. +*/ +static void +syserr(int num, char *fmt, ...) +{ + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fprintf(stderr, " - %s\n", os_strerror(num)); + + errpt(); +} + + +/* PANIC - called while the KLH10 is actually running, whenever something +** detects a situation that should be impossible or cannot be handled. +*/ +void +panic(char *fmt, ...) +{ + fprintf(stderr, "\r\nKLH10 PANIC: "); + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fprintf(stderr, "\r\n Current PC= %#lo\r\n", (long) PC_30); + + errpt(); /* For now, break directly to main loop */ +} + +/* Startup arg parsing and default setting. +** For now, all we do with args is interpret the first as a command file. +** If no arg, look for KLH10_INITFILE as default init command file. +*/ + +#define KLH10_SWITCHES \ + swidef(SWI_BKGD, "background"), /* Run in bkgnd mode */\ + swidef(SWI_HELP, "help"), /* Not yet */\ + swidef(SWI_VERSION, "version") /* Not yet */ +enum { +# define swidef(i,s) i + KLH10_SWITCHES +# undef swidef +}; + +static char *switab[] = { +# define swidef(i,s) s + KLH10_SWITCHES +# undef swidef + , NULL +}; + +static char usage[] = "Usage: klh10 [-background] [initfile]\n"; + +void +swinit(int ac, char **av) +{ + FILE *f; + char *initfile = NULL; + char *cp; + int res, kx1, kx2; + + while (--ac > 0) { + cp = *(++av); + if (*cp != '-') { + /* Arg that isn't a switch is assumed to be init filename */ + if (initfile) { /* If already have it, error */ + fprintf(stderr, "%s", usage); + os_exit(1); + } + initfile = cp; + continue; + } + res = s_xkeylookup(cp+1, (void *)switab, sizeof(switab[0]), + (voidp_t *)NULL, (voidp_t *)NULL, &kx1, &kx2); + if (res == 1) switch (kx1) { + case SWI_BKGD: + proc_bkgd = TRUE; + continue; + } + + /* Fall through to here if bad switch or something else wrong */ + fprintf(stderr, "Unknown switch \"%s\"\n", cp); + fprintf(stderr, "%s", usage); + os_exit(1); + } + + if (!initfile) { /* If no init file explicitly given */ + initfile = KLH10_INITFILE; /* Use default, but */ + f = fopen(initfile, "r"); /* if not there, don't complain */ + } else if (!(f = fopen(initfile, "r"))) { + syserr(-1, "Cannot open \"%s\"", initfile); + /* Doesn't return since init not yet finished */ + } + + if (f) { + cminfile = f; /* Set - read cmds from file until gone */ + cminfname = initfile; /* Remember its name */ + } + + /* Set any other defaults necessary */ + ld_fmt = ld_dfmt; /* Set current load/dump format to default */ +} + +/* Command parsing routines. +** New full-line version, still simple-minded. +** Eventually can go to CCMD-like or GDB-like package, but for now keep +** the overall size small. +** +** Some of the parsing functions are in PRMSTR so they can readily be +** called by other modules (specifically device emulation code). +*/ + +void cmdreset(struct cmd_s *); +void cmdspfls(struct cmd_s *); +static void stolower(char *); +static int smatch(char *, char *); + +static int cmdargs_none(struct cmd_s *cm); +static int cmdargs_one(struct cmd_s *cm, char **); +static int cmdargs_two(struct cmd_s *cm, char **, char **); +static int cmdargs_all(struct cmd_s *cm); +static int cmdargs_n(struct cmd_s *cm, int n); + +void cmdinit(struct cmd_s *cm, + char *prompt, + char *ibuf, + size_t ilen) +{ + cm->cmd_flags = 0; + cm->cmd_prm = prompt; + cm->cmd_buf = ibuf; + cm->cmd_blen = ilen; + cmdreset(cm); +} + +void +cmdreset(struct cmd_s *cm) +{ + cm->cmd_flags &= ~(CMDF_ACTIVE|CMDF_INACCUM); + cm->cmd_left = cm->cmd_blen; + cm->cmd_inp = cm->cmd_rdp = cm->cmd_buf; + cm->cmd_inp[0] = '\0'; + cm->cmd_rleft = 0; + cm->cmd_arglin = NULL; + cm->cmd_argc = 0; + cm->cmd_argv[0] = NULL; + cm->cmd_tdp = cm->cmd_tokbuf; + cm->cmd_tleft = sizeof(cm->cmd_tokbuf)-1; +} + +static int +cminchar(void) +{ + register int ch; + + if (!cminfile) + return os_ttyin(); + + if ((ch = getc(cminfile)) == EOF) { + fclose(cminfile); + cminfile = NULL; + fprintf(stdout, "[EOF on \"%s\"]\n", cminfname); + ch = '\n'; /* Try to end gracefully */ + } else { + putc(ch, stdout); /* Echo file input char */ + os_ttycmforce(); /* Ensure it's out */ + } + return ch; +} + +int +cmdaccum(struct cmd_s *cm) +{ + register int ch; + + /* Output prompt if not already there */ + if ((cm->cmd_flags & CMDF_INACCUM) == 0) { + printf("%s", cm->cmd_prm ? cm->cmd_prm : ">"); + cm->cmd_flags |= CMDF_INACCUM; + os_ttycmforce(); /* Force out any pending tty output */ + } + + for (;;) { + if ((ch = cminchar()) < 0) /* Get char from file or TTY */ + break; /* Break out if EOF */ + + if (cm->cmd_left <= 1) { + error("Command line overflow (%d chars!); \"%.10s...\" flushed.\n", + (int)cm->cmd_blen, cm->cmd_buf); +#if 0 + os_ttyinflush(); /* Flush any pending input */ +#endif + cmdreset(cm); + return cmdaccum(cm); + } + + *(cm->cmd_inp)++ = ch; + --(cm->cmd_left); + + /* Again, simple built-in handling; no dynamic specification of + ** activation chars. + */ + switch (ch) { + /* See if last char was activation char */ + case '\r': + case '\n': + cm->cmd_flags |= CMDF_ACTIVE; /* Got one! */ + *(cm->cmd_inp) = '\0'; + return TRUE; + } + } + return FALSE; /* No more input */ +} + +/* CMDEXEC - Called when we're known to have an activated command in buffer. +** Parse first keyword to see if it's anything recognizable, and +** invoke appropriate function if so. +*/ +int +cmdexec(struct cmd_s *cm) +{ + register char *cp; + char tokbuf[100]; + char *tcp = tokbuf; + size_t tcnt = sizeof(tokbuf); + size_t fcnt = cm->cmd_inp - cm->cmd_rdp; /* # chars left to read */ + struct cmkey_s *key, *key2; + register struct cmrtn_s *cd; + int argc; + + /* Get first token on line */ + cp = s_1token(&tcp, &tcnt, &cm->cmd_rdp, &fcnt); + if (!cp) { /* If no token (empty line) */ + return 0; /* just return having done nothing */ + } + if (*cp == '\n' || *cp == '\r') /* If EOL, just return */ + return 0; /* without adding our own echo */ + if (*cp == ';') /* If start of comment, */ + return 0; /* ditto */ + + /* Have token, see if it's a command */ + stolower(cp); /* Force lowercase for lookup */ + argc = s_keylookup(cp, fectbkeys, sizeof(struct cmkey_s), + (void *)&key, (void *)&key2); + if (argc <= 0) { + printf("Unknown command: \"%s\"\n", cp); + return 0; + } + if (argc > 1) { + printf("Ambiguous command: \"%s\" => %s, %s%s\n", + cp, key->cmk_key, key2->cmk_key, + argc > 2 ? ", ..." : ""); + return 0; + } + + /* Found a single match! Execute it... */ + /* First set up its args as indicated by flags */ + cd = &(key->cmk_p->cmn_rtn); /* Get ptr to routine definition */ + cmdspfls(cm); /* Flush initial spaces from line */ + cm->cmd_arglin = cm->cmd_rdp; + cm->cmd_rleft = strlen(cm->cmd_rdp); + if (cd->cmr_flgs & CMRF_CMPTR) { + (*cd->cmr_vect)(cm); /* Special handling, no arg hackery */ + return 1; + } + slinlim(cm->cmd_rdp); /* Ensure no EOL in line */ + cm->cmd_rleft = strlen(cm->cmd_rdp); + if (cd->cmr_flgs & CMRF_NOARG) { + if (cm->cmd_rleft) { + printf("Bad syntax - no args allowed\n"); + return 0; + } + (*cd->cmr_vect)(cm); + return 1; + } else if (cd->cmr_flgs & CMRF_TLIN) { + /* That's all it wants - cmd_rleft will be set but cmd_argc is 0 */ + (*cd->cmr_vect)(cm); + return 1; + } + argc = cmdargs_all(cm); + if (cd->cmr_flgs & CMRF_TOKS) { + (*cd->cmr_vect)(cm); /* Invoke with all tokens set up */ + return 1; + } else return 0; + + return 1; +} + + +char * +cmdlsetup(struct cmd_s *cm) +{ + char *cp; + size_t len; + + if (!(cm->cmd_flags & CMDF_NOPRM) && cm->cmd_prm) { + fputs(cm->cmd_prm, stdout); + } + + os_ttycmforce(); /* Force out any pending tty output */ + cmdreset(cm); + if (cminfile) { + cp = fgets(cm->cmd_inp, (int)cm->cmd_left-1, cminfile); + if (!cp) { + fprintf(stdout, "[EOF on %s]\n", cminfname); + fclose(cminfile); + cminfile = NULL; + return cmdlsetup(cm); /* Try again using TTY */ + } + fputs(cp, stdout); /* Echo the input line */ + } else { + cp = os_ttycmline(cm->cmd_inp, (int)cm->cmd_left-1); + if (cp == NULL) + return NULL; + } + + len = strlen(cp); + cm->cmd_left -= len; + cm->cmd_inp += len; + + return cp; +} + + +/* CMDFLS - Flush whitespace from current command pos +*/ +void +cmdspfls(register struct cmd_s *cm) +{ + register char *cp = cm->cmd_rdp; + + if (cp) + while (isspace(*cp)) ++cp; + cm->cmd_rdp = cp; +} + +/* Helper functions for FC_ commands */ + +static int +cmdargs_none(register struct cmd_s *cm) +{ + if (cm->cmd_rleft || cm->cmd_argc > 0) { + printf("Bad syntax - no args allowed\n"); + return 0; + } + return 1; +} + +static int +cmdargs_one(register struct cmd_s *cm, char **as1) +{ + if (cm->cmd_argc == 1) { + *as1 = cm->cmd_argv[0]; + return 1; + } + if (cm->cmd_argc > 1) { + printf("Bad syntax - too many args\n"); + } else + printf("Bad syntax - no arg\n"); + return 0; +} + +static int +cmdargs_two(register struct cmd_s *cm, char **as1, char **as2) +{ + if (cm->cmd_argc == 2) { + *as1 = cm->cmd_argv[0]; + *as2 = cm->cmd_argv[1]; + return 1; + } + printf("Bad syntax - need two args\n"); + return 0; +} + +static int +cmdargs_all(register struct cmd_s *cm) +{ + /* See if haven't yet tokenized line and there's something on it */ + if (cm->cmd_argc == 0 && cm->cmd_rleft) { + cm->cmd_argc = s_tokenize(cm->cmd_argv, + CMDMAXARG, + &cm->cmd_tdp, &cm->cmd_tleft, + &cm->cmd_rdp, &cm->cmd_rleft); + cm->cmd_argv[cm->cmd_argc] = NULL; + } + return cm->cmd_argc; /* Return # of tokens */ +} + +static int +cmdargs_n(register struct cmd_s *cm, int n) +{ + /* See if haven't yet tokenized line and there's something on it */ + if (cm->cmd_argc == 0 && cm->cmd_rleft) { + if (n > CMDMAXARG) + n = CMDMAXARG; + cm->cmd_argc = s_tokenize(cm->cmd_argv, + n, + &cm->cmd_tdp, &cm->cmd_tleft, + &cm->cmd_rdp, &cm->cmd_rleft); + cm->cmd_argv[cm->cmd_argc] = NULL; + } + return cm->cmd_argc; /* Return # of tokens parsed */ +} + +#ifndef CMVPAR_INCLUDED /* Using external parser stuff? */ +# define CMVPAR_INCLUDED 0 /* Nope, use our own */ +#endif + +#if !CMVPAR_INCLUDED + /* Use own internal parsing code */ + +struct cmkey_s * +cmdkeylookup(char *cp, + register struct cmkey_s *keytab, + register struct cmkey_s **key2) +{ + struct cmkey_s *key; + + (void) s_keylookup(cp, (voidp_t)keytab, sizeof(struct cmkey_s), + (voidp_t*)&key, (voidp_t*)key2); + return key; +} + +#endif /* !CMVPAR_INCLUDED */ + +static void +fc_ques(struct cmd_s *cm) +{ + printf("Type \"help\" or \"help \" for help.\n"); +} + +static void +fc_help(struct cmd_s *cm) +{ + struct cmkey_s *kp, *key2; + int cols; + register char *cp; + + /* Get first token on line. OK if NULL. */ + cp = cm->cmd_argv[0]; + + if (!cp || !*cp) { /* If no specific arg, show everything */ + for (kp = fectbkeys; kp->cmk_key; ++kp) { + if ((cols = 20 - strlen(kp->cmk_key)) < 0) + cols = 0; + if (!(cp = kp->cmk_p->cmn_rtn.cmr_synt)) + cp = ""; + if (cols < strlen(cp)) /* Key and syntax too long? */ + printf("%s %s\n %s\n", + kp->cmk_key, cp, kp->cmk_p->cmn_rtn.cmr_help); + else + printf("%s %-*s %s\n", + kp->cmk_key, cols, cp, kp->cmk_p->cmn_rtn.cmr_help); + } + return; + } + + (void) s_keylookup(cp, (voidp_t)fectbkeys, sizeof(struct cmkey_s), + (voidp_t *)&kp, (voidp_t *)&key2); + if (!kp) { + printf("Unknown command: \"%s\"\n", cp); + return; + } + if (!key2) { /* Just one match, so show it in detail */ + printf("%s %s\n%s\n", kp->cmk_key, + kp->cmk_p->cmn_rtn.cmr_help, + kp->cmk_p->cmn_rtn.cmr_desc); + return; + } + + /* More than one match, show each one */ + for (kp = fectbkeys; kp->cmk_key; ++kp) { + if (smatch(cp, kp->cmk_key) > 0) + printf("%s %s\n", kp->cmk_key, kp->cmk_p->cmn_rtn.cmr_help); + } +} + +/* SLINLIM - Limit string to 1 line by chopping it at first EOL seen. +*/ +static void +slinlim(register char *s) +{ + for (; *s; ++s) + if (*s == '\r' || *s == '\n') { + *s = '\0'; + break; + } +} + +static void +stolower(register char *s) +{ + for (; *s; ++s) + if (isupper(*s)) + *s = tolower(*s); +} + +/* SMATCH - compare strings +** Returns 0 if mismatched +** 1 if S1 is initial substring of S2 +** 2 if S1 is exact match of S2 +*/ +static int +smatch(register char *s1, + register char *s2) +{ + while (*s1) { + if (*s1++ != *s2++) + return 0; + } + return *s2 ? 1 : 2; +} + +/* KLH10_INIT - Actually an internal routine to initialize and configure +** the emulator. Could be considered analogous to "loading the +** microcode"... +*/ +static void +klh10_init(void) +{ + +#if KLH10_SYS_T20 && KLH10_CPU_KS +# if KLH10_CTYIO_ADDINT + cpu.fe.cty_lastint = 20; /* 20ms delay to add extra output-done int */ +# else + cpu.fe.fe_iowait = 50; /* Alternative for avoiding T20 CTY lossage */ +# endif +#endif + cpu.fe.fe_intchr = '\034'; /* Default int char is FS (ctrl-\) */ + cpu.mr_exsafe = 2; /* Default to exec-mode safety check/halt */ + + /* Move this to pag_init? */ + /* Some checking that couldn't be done at compile time */ + if (VMF_ACC & PAG_PAMSK) { + error("Pager access bits (%lx) overlap phys page mask (%lx)!\n", + (long)VMF_ACC, (long)PAG_PAMSK); + } + + + op_init(); /* Initialize runtime opcode dispatch tables */ +#if (KLH10_CPU_KS || KLH10_CPU_KL) && (KLH10_SYS_T10 || KLH10_SYS_T20) + { + extern void inexts_init(void); + inexts_init(); /* Initialize EXTEND instruction stuff */ + } +#endif + + /* Now can add in any special runtime cases */ + + mem_init(); /* Initialize memory */ + apr_init(); /* Initialize APR stuff (includes internal devs) */ + dev_init(); /* And general IO device stuff */ + cty_init(); /* And console TTY stuff */ +} + +/* MEM_INIT - Allocate & initialize PDP10 memory. +** Perhaps move this to pag_init? +*/ +static void +mem_init(void) +{ + size_t memsiz; + char *ptr; + + /* Determine amount of phys mem to get */ + memsiz = (size_t)PAG_SIZE * PAG_MAXPHYSPGS * sizeof(w10_t); + + /* Always attempt to get shared memory first. If that fails + ** (either because OS can't do it, or KLH10 was compiled without + ** support), then go for private memory. + */ + fprintf(stdout, "[MEM: Allocating %ld pages ", (long)PAG_MAXPHYSPGS); + if (os_mmcreate(memsiz, &cpu.mm_physegid, &ptr)) { + cpu.mm_shared = TRUE; + fprintf(stdout, "shared memory, clearing..."); + } else { + /* Failed - routine already barfed about it, if OS failure. */ + cpu.mm_shared = FALSE; + + /* Allocate private 10 memory */ + cpu.mm_physegid = 0; + fprintf(stdout, "private memory, clearing..."); + if (!(ptr = malloc(memsiz))) { + syserr(errno, "MEM: cannot malloc KN10 physical memory!\n"); + } + } + + cpu.physmem = (vmptr_t)ptr; + + /* To avoid unpleasant surprises to code that expects unused bits + ** of w10_t words to be zero, we clear it all to begin with. + */ + memset(ptr, 0, memsiz); /* Clear it all out */ + fprintf(stdout, "done]\n"); +} + +/* MEM_TERM - Terminate (de-init) memory stuff. +** Mainly intended to clean up shared-seg stuff. May later be useful +** for re-configuring memory. +** Will of course bomb completely if anything refs phys mem after +** this call... +*/ +static void +mem_term(void) +{ + if (cpu.mm_shared) { + os_mmkill(cpu.mm_physegid, (char *)cpu.physmem); + cpu.mm_physegid = 0; + } else { + free((char *)cpu.physmem); + } + cpu.physmem = NULL; + cpu.mm_shared = FALSE; +} + +static int +mem_setlock(FILE *of, FILE *ef, int nlockf) +{ + if (nlockf == cpu.mm_locked) { + if (of) + fprintf(of, "Memory already %slocked\n", nlockf ? "" : "un"); + return TRUE; + } + if (os_memlock(nlockf)) { + if (of) + fprintf(of, "Memory %slocked\n", nlockf ? "" : "un"); + cpu.mm_locked = nlockf; + return TRUE; + } else { + if (ef) + fprintf(ef, "Memory could not be %slocked - %s\n", + (nlockf ? "" : "un"), os_strerror(-1)); + return FALSE; + } +} + +/* Support routines for FC_SET - parameter parsing +*/ + +static int cmvp_sethz(struct prmvcx_s *); +static int cmvp_setpri(struct prmvcx_s *); +static int cmvp_memlock(struct prmvcx_s *); + +extern int ld_debug; /* From feload.c */ +#if KLH10_DEBUG && KLH10_CPU_KS +extern int tim_debug; /* From kn10cpu.c */ +#endif + +struct prmvar_s fecmvars[] = { + PRMVAR("sw", "Data switch word", + PRMVT_WRD, &cpu.mr_dsw, NULL, NULL), + PRMVAR("mem_lock", "Set on to attempt locking memory", + PRMVT_BOO, &cpu.mm_locked, cmvp_memlock, NULL), + PRMVAR("proc_pri", "CPU process priority", + PRMVT_DEC, &proc_pri, cmvp_setpri, NULL), + PRMVAR("cpu_debug", "General CPU debug trace", + PRMVT_BOO, &cpu.mr_debug, NULL, NULL), + PRMVAR("cpu_exsafe", "Enable exec mode safety halts", + PRMVT_OCT, &cpu.mr_exsafe, NULL, NULL), + PRMVAR("fe_intchr", "KLH10 cmd escape char", + PRMVT_OCT, &cpu.fe.fe_intchr, NULL, NULL), + PRMVAR("fe_prompt", "KLH10 cmd prompt", + PRMVT_STR, &cmdprompt, NULL, NULL), + PRMVAR("cty_debug", "CTY debug trace", + PRMVT_BOO, &cty_debug, NULL, NULL), +#if KLH10_SYS_T20 && KLH10_CPU_KS + PRMVAR("cty_iowait", "CTY output delay, usec", + PRMVT_DEC, &cpu.fe.fe_iowait, NULL, NULL), +# if KLH10_CTYIO_ADDINT + PRMVAR("cty_lastint", "CTY last-char extra output int, msec", + PRMVT_DEC, &cpu.fe.cty_lastint, NULL, NULL), +# endif +#endif +#if KLH10_DEBUG && KLH10_CPU_KS + PRMVAR("tim_debug", "KS timebase debug trace", + PRMVT_BOO, &tim_debug, NULL, NULL), +#endif + PRMVAR("ld_fmt", "LOAD/DUMP word format", + PRMVT_STR, &ld_fmt, NULL, NULL), + PRMVAR("ld_debug", "LOAD debug trace", + PRMVT_BOO, &ld_debug, NULL, NULL), + PRMVAR("insbreak", "APR loop interrupt", + PRMVT_DEC, &cpu.mr_insbreak, NULL, NULL), +#if KLH10_CLKTRG_COUNT + PRMVAR("clk_ipms", "Instrs per virt msec", + PRMVT_DEC, &cpu.clk.clk_ipmsrq, + cmvp_sethz,NULL), +#elif KLH10_CLKTRG_OSINT + PRMVAR("clk_ithz", "OS interval timer - current value in Hz", + PRMVT_DEC, &cpu.clk.clk_ithzcmreq, + cmvp_sethz, NULL), + PRMVAR("clk_ithzfix", "ITimer value fixed at this if non-zero", + PRMVT_DEC, &cpu.clk.clk_ithzfix, + cmvp_sethz, NULL), + PRMVAR("clk_ithzosreq", "ITimer value last requested by OS", + PRMVT_DEC, &cpu.clk.clk_ithzosreq, + NULL, NULL), +#endif + PRMVAR("pisys_on", "Set if PI sys on", + PRMVT_OCT, &cpu.pi.pisys_on, NULL, NULL), + PRMVAR("pilev_on", "Levs enabled", + PRMVT_OCT, &cpu.pi.pilev_on, NULL, NULL), + PRMVAR("pilev_pip", "Levs PI in Progress", + PRMVT_OCT, &cpu.pi.pilev_pip, NULL, NULL), + PRMVAR("pilev_preq", "Prog PI reqs", + PRMVT_OCT, &cpu.pi.pilev_preq, NULL, NULL), + PRMVAR("pilev_aprreq", "APR PI reqs", + PRMVT_OCT, &cpu.pi.pilev_aprreq, NULL, NULL), + PRMVAR("pilev_dreq", "Device PI reqs", + PRMVT_OCT, &cpu.pi.pilev_dreq, NULL, NULL), +#if KLH10_CPU_KS + PRMVAR("pilev_ub1req", "UBA #1 PI reqs", + PRMVT_OCT, &cpu.pi.pilev_ub1req, NULL, NULL), + PRMVAR("pilev_ub3req", "UBA #3 PI reqs", + PRMVT_OCT, &cpu.pi.pilev_ub3req, NULL, NULL), +#endif +#if KLH10_CPU_KL + PRMVAR("pilev_rhreq", "RH20 PI reqs", + PRMVT_OCT, &cpu.pi.pilev_rhreq, NULL, NULL), + PRMVAR("pilev_dtereq", "DTE20 PI reqs", + PRMVT_OCT, &cpu.pi.pilev_dtereq, NULL, NULL), +#endif + PRMVAR(NULL, "", PRMVT_NULL, NULL, NULL, NULL) +}; + +/* Various parameter get/set auxiliary functions */ + +static int +cmvp_memlock(register struct prmvcx_s *cx) +{ + return mem_setlock(cx->prmvcx_of, cx->prmvcx_ef, cx->prmvcx_val.vi); +} + +static int +cmvp_setpri(register struct prmvcx_s *cx) +{ + ospri_t npri = cx->prmvcx_val.vi; + ospri_t opri; + + if (os_setpriority(npri) == FALSE) { + if (cx->prmvcx_ef) + fprintf(cx->prmvcx_ef, "Could not change priority - %s\n", + os_strerror(-1)); + return FALSE; + } + if (os_getpriority(&opri) == FALSE) { + if (cx->prmvcx_ef) + fprintf(cx->prmvcx_ef, "Could not find priority - %s\n", + os_strerror(-1)); + return FALSE; + } + if (cx->prmvcx_of) { + if (opri == proc_pri) + fprintf(cx->prmvcx_of, "Process priority remains %ld\n", + (long)opri); + else + fprintf(cx->prmvcx_of, "Process priority changed from %ld to %ld\n", + (long)proc_pri, (long)opri); + } + proc_pri = opri; + return TRUE; +} + +static int +cmvp_sethz(register struct prmvcx_s *cx) +{ + /* First just set requested value normally */ + if (!prmvp_set(cx)) + return FALSE; /* Problem setting param? Already reported */ + + /* Then must do special re-init stuff */ +#if KLH10_CLKTRG_COUNT + clk_ipmsset(cpu.clk.clk_ipmsrq); +#elif KLH10_CLKTRG_OSINT + clk_ithzset(cpu.clk.clk_ithzcmreq); +#endif + return TRUE; +} + + +static int +addrparse(register char *str, + vaddr_t *vloc, + enum fevmmode *mloc) +{ + w10_t w; + enum fevmmode mode = FEVM_DFLT; + int local = FALSE, global = FALSE; + + if (!str) return FALSE; + while (isalpha(*str)) { + switch (islower(*str) ? toupper(*str++) : *str++) { + case 'L': local = TRUE; break; + case 'G': global = TRUE; break; + case 'C': mode = FEVM_CUR; break; + case 'P': mode = FEVM_PHYS; break; + case 'E': mode = FEVM_EXEC; break; + case 'U': mode = FEVM_USER; break; + case 'A': mode = FEVM_ACB; break; + default: + return 0; /* Bad syntax, unknown char */ + } + } + *mloc = mode; + + /* Note distinction between 123456 and 0,,123456 + ** (ie explicit specification of section #) + */ + switch (s_towd(str, &w)) { + default: /* Bad syntax of address */ + return FALSE; + case 1: /* One value: */ + if (!local && !global) /* If not otherwise specified, */ + local = TRUE; /* defaults to local */ + break; + case 2: /* Two values: ,, */ + if (!local && !global) /* If not otherwise specified, */ + global = TRUE; /* defaults to global */ + break; + } + + /* Problem if local - where does default section # come from? + ** Depends on mode, but even the mode gets hairy. + */ + if (mode == FEVM_ACB) { /* Special hack, ac block # in LH */ + if ((LHGET(w) & ~(h10_t)07) /* Only allow blocks 0-7 */ + || (RHGET(w) & ~(h10_t)AC_MASK)) /* and ACs 0-017 */ + return FALSE; + va_hmake(*vloc, LHGET(w), RHGET(w)); + return TRUE; + } + if (local) + va_lmake(*vloc, LHGET(w) & VAF_SMSK, RHGET(w)); + else + va_gmake(*vloc, LHGET(w) & VAF_SMSK, RHGET(w)); + return TRUE; +} + + +/* FC_SET - Set/Show KLH10 variables +*/ +static void +fc_set(struct cmd_s *cm) +{ + struct prmvar_s *p1, *p2; + int res; + char *cp = cm->cmd_arglin; + + if (!cp || !*cp /* If no arg, show all vars */ + || *cp == '?') { /* Ditto if starts with ? */ + for (p1 = fecmvars; p1->prmv_name; ++p1) + prm_varshow(p1, stdout); + return; + } + if (!strchr(cp, '=')) { /* If doesn't look like var=val */ + res = s_keylookup(cp, (voidp_t)fecmvars, sizeof(*p1), + (voidp_t*)&p1, (voidp_t*)&p2); + if (res == 1) { + prm_varshow(p1, stdout); + } else if (res > 1) { + fprintf(stdout, "Ambiguous variable: \"%s\", \"%s\"%s\n", + p1->prmv_name, p2->prmv_name, (res > 2 ? ", ..." : "")); + } else { + fprintf(stdout, "Unknown var or bad syntax, must be =\n"); + } + return; + } + + prm_varset(&cp, fecmvars, stdout, stdout); +} + + +/* Device control commands */ + +/* FC_DEVLOAD - Load a dynamic/shared library as a device driver, +** and defines its name for later use in configuration. +** +** Syntax is: +** devload [] +** +** where +** - Arbitrary device driver name. +** - Pathname for library in native OS. +** - "Entry point" symbol (init/config routine) +** - Optional description text +*/ +static void +fc_devload(struct cmd_s *cm) +{ + if (cmdargs_n(cm, 3) != 3) { + printf("?Bad syntax - usage: %s\n", cd_devload.cmr_synt); + return; + } + stolower(cm->cmd_argv[0]); /* Driver name forced to lowercase */ + (void) dev_drvload(stdout, + cm->cmd_argv[0], + cm->cmd_argv[1], + cm->cmd_argv[2], + cm->cmd_rdp); +} + + +/* FC_DEVDEF - Do initial device configuration +** Defines the device name and does its initial configuration setup. +** +** Syntax is: +** devdefine { <10dev> } [] +** { UB } +** where +** - Name to identify device +** <10dev> - A PDP-10 device number, or predefined keyword (CTY, etc) +** Implies device uses dev-IO instructions. +** UB - A Unibus controller (n = 1 or 3) +** Implies device uses KS IO instructions. +** - Name of a driver module, either builtin or dynamic. +** - Optional args to dev's init routine (rest of line) +** +** NOTE: this replaces old design of: +** devconf { <10dev> } {static } +** { UB } {extern } +** but the static/extern option has been replaced by DEVLOAD. +*/ +static void +fc_devdef(struct cmd_s *cm) +{ + if (cmdargs_n(cm, 3) != 3) { + printf("?Bad syntax - usage: %s\n", cd_devdef.cmr_synt); + return; + } + stolower(cm->cmd_argv[0]); /* Driver name forced to lowercase */ + stolower(cm->cmd_argv[1]); /* Device number ditto */ + stolower(cm->cmd_argv[2]); /* Driver name ditto */ + (void) dev_define(stdout, + cm->cmd_argv[0], + cm->cmd_argv[1], + cm->cmd_argv[2], + cm->cmd_rdp); +} + + +static void +fc_devshow(struct cmd_s *cm) +{ + /* Pass first token on line. OK if NULL. */ + (void) cmdargs_n(cm, 1); + (void) dev_show(stdout, cm->cmd_argv[0], cm->cmd_rdp); +} + +#if KLH10_EVHS_INT +static void +fc_devevshow(struct cmd_s *cm) +{ + /* Pass first token on line. OK if NULL. */ + (void) cmdargs_n(cm, 1); + (void) dev_evshow(stdout, cm->cmd_argv[0], cm->cmd_rdp); +} +#endif /* KLH10_EVHS_INT */ + +/* FC_DEVSET - Hack defined device from 10 side +** For use when need to set device-related things from the 10's side of +** the fence, for a specific device. +** Syntax: +** devset = [devvar2>= ...] +*/ +static void +fc_devset(char *argline) +{ +} + + +/* FC_DEV_CMD - Generic device command +** Syntax: dev +** +** Invokes device's command parsing and execution, which can be anything +** the device wants to do with the line. Most should follow certain +** conventions, however: +** dev help - Show what this device understands +** dev status - Show status in appropriate form +** dev show [param] [...] - Show specific device vars +** dev set [param=val] [...] - Set them +*/ +static void +fc_dev_cmd(struct cmd_s *cm) +{ + /* Consume first token on line - must exist */ + if (cmdargs_n(cm, 1) != 1) { /* If no token */ + printf("Bad dev cmd syntax, need device specifier\n"); + return; + } + (void) dev_command(stdout, cm->cmd_argv[0], cm->cmd_rdp); +} + + +static void +fc_dev_help(struct cmd_s *cm) +{ + /* Pass first token on line. OK if NULL. */ + (void) cmdargs_n(cm, 1); + (void) dev_help(stdout, cm->cmd_argv[0], cm->cmd_rdp); +} + +static void +fc_dev_status(struct cmd_s *cm) +{ + /* Pass first token on line. OK if NULL. */ + (void) cmdargs_n(cm, 1); + (void) dev_status(stdout, cm->cmd_argv[0], cm->cmd_rdp); +} + +static void +fc_devmnt(struct cmd_s *cm) +{ + char *spath; + + /* Consume first two tokens on line - must exist */ + switch (cmdargs_n(cm, 2)) { + case 0: + printf("Bad syntax - must be specified\n"); + return; + case 1: + spath = ""; /* Ensure 2nd arg non-null as NULL means dismount */ + break; + default: + spath = cm->cmd_argv[1]; + break; + } + (void) dev_mount(stdout, cm->cmd_argv[0], spath, cm->cmd_rdp); + fedevchkf = dev_dpchk_ctl(TRUE); /* Start periodic checks if nec */ +} + +static void +fc_devunmnt(struct cmd_s *cm) +{ + /* Consume first token on line - must exist */ + if (cmdargs_n(cm, 1) != 1) { /* If no token */ + printf("Bad syntax - must be specified\n"); + return; + } + (void) dev_mount(stdout, cm->cmd_argv[0], (char *)NULL, cm->cmd_rdp); + fedevchkf = dev_dpchk_ctl(TRUE); /* Start periodic checks if nec */ +} + +static void +fc_devdbg(struct cmd_s *cm) +{ + /* Pass first two tokens on line. OK if NULL. */ + if (cmdargs_n(cm, 2) < 1) + cm->cmd_argv[1] = NULL; + (void) dev_debug(stdout, cm->cmd_argv[0], cm->cmd_argv[1], cm->cmd_rdp); +} + +static void +fc_devwait(struct cmd_s *cm) +{ + char *dev; + long totsec = -1; + + /* Consume first two tokens on line. OK if NULL. */ + switch (cmdargs_n(cm, 2)) { + case 0: + dev = NULL; /* All devices */ + totsec = -1; /* Indefinitely */ + break; + case 1: /* One arg, either or */ + dev = cm->cmd_argv[0]; + if (s_todnum(dev, &totsec)) + dev = NULL; /* Numeric, is */ + else + totsec = -1; /* Not numeric, is */ + break; + default: + dev = cm->cmd_argv[0]; + if (!s_todnum(cm->cmd_argv[1], &totsec)) { + printf("Bad syntax - \"%s\" must be timeout in secs\n", + cm->cmd_argv[1]); + return; + } + break; + } + + /* OK, now start the wait. */ + while (dev_waiting(stdout, dev)) { + if (totsec > 0 && --totsec == 0) + break; /* Stop waiting if timed out */ + os_sleep(1); + } +} + +/* FC_DEVBOOT - Boot using specified device. +** devboot [halt] +** Uses device's "read-in mode" to read in a small piece of +** bootstrap code and starts execution there. +** If option "halt" given, doesn't actually start PDP-10, +** just sets start addr so if continued will start bootstrap. +*/ +static void +fc_devboot(struct cmd_s *cm) +{ + int opthalt = FALSE; + vaddr_t bootsa; + + /* Consume first two tokens on line */ + switch (cmdargs_n(cm, 2)) { + case 0: + printf("Bad syntax - must be specified\n"); + return; + case 1: + break; + default: + if (strcasecmp(cm->cmd_argv[1], "halt")==0) + opthalt = TRUE; + else { + printf("Unknown option \"%s\"\n", cm->cmd_argv[1]); + return; + } + break; + } + + if (!dev_boot(stdout, cm->cmd_argv[0], &bootsa)) { + printf("Bootstrap readin of %s failed\n", cm->cmd_argv[0]); + return; + } + + /* Success! Set up returned boot address as both new PC and new + start address, and maybe go. + */ + ddt_loadsa = bootsa; + PC_SET(bootsa); + if (opthalt) { + printf("Bootstrap read in, halted with PC = %lo\n", (long)bootsa); + return; + } + printf("Bootstrap read in\n"); + cpu.mr_1step = 0; + aprcont(1, bootsa); +} + +/* FC_RESET - Halts and Resets PDP-10 (clears all status) +*/ +static void +fc_reset(struct cmd_s *cm) +{ + fc_halt(cm); /* Ensure halted if not already */ + apr_init(); +} + +/* FC_GO - Starts PDP-10 at given location. Defaults to last +** loaded start address, if any. +*/ +static void +fc_go(struct cmd_s *cm) +{ + vaddr_t loc; + enum fevmmode mode; + char *sloc = cm->cmd_arglin; + + if (sloc && *sloc) { + if (!addrparse(sloc, &loc, &mode)) { + printf("?Bad address\n"); + return; + } else if (mode != FEVM_DFLT && mode != FEVM_CUR) { + printf("?Bad address mode - only Current allowed\n"); + return; + } else if (va_isglobal(loc)) { + printf("?Bad address - only local allowed\n"); + return; + } + ddt_loadsa = loc; /* Mode will always be current */ + } + cpu.mr_1step = 0; + aprcont(1, ddt_loadsa); +} + +/* FC_CONT - Continues PDP-10 at current PC. +*/ +static void +fc_cont(struct cmd_s *cm) +{ + cpu.mr_1step = 0; + aprcont(-1, 0); +} + +/* APRCONT - Resume execution. +** +1 start, 0 continue, -1 continue verbosely +*/ +static void +aprcont(int startf, + vaddr_t newpc) +{ + int res; + + if (!aprhalted()) { + printf("PDP10 still running! Halt or Reset it first.\n"); + return; + } + if (fedevchkf) + fedevchkf = dev_dpchk_ctl(FALSE); /* Turn off dev checking! */ + if (startf > 0) + PC_SET(newpc); + + if (startf) + printf("%s KN10 at loc %#lo...\n", + (startf>0 ? "Starting" : "Continuing"), + (long)PC_30); + + /* Set up TTY handling appropriately for running */ + cty_enable(); /* No echo, no CR/LF hacks, no delay */ + res = apr_run(); /* Go! */ + cty_disable(); /* Restore normal mode */ + + switch (res) { + case HALT_PROG: /* Program halt (JRST 4,) */ + printf("[HALTED: Program Halt, PC = %lo]\n", (long)PC_30); + break; + case HALT_FECTY: /* FE Console interrupt */ + printf("[HALTED: FE interrupt]\n"); + break; + + case HALT_BKPT: /* Hit breakpoint */ + printf("[HALTED: Breakpoint]\n"); + break; + + case HALT_STEP: /* Single-Stepping */ + break; + + case HALT_EXSAFE: /* Something bad in exec mode */ + printf("[HALTED: Exec program error? (\"set cpu_exsafe=1\" to continue)]\n"); + break; + + case HALT_PANIC: /* Panic - internal error, bad state */ + printf("[HALTED: Panic - may be in inconsistent state]\n"); + break; + } +} + + +static int aprhalted(void) +{ + return TRUE; +} + +/* FC_SHUTDOWN - Halts PDP-10 OS gracefully if possible, by +** putting cruft in the shutdown location of the FECOM area. +** Assumes physical mapping, as does the CTY code. +** +** KA/KI versions would probably set the data switches here. +*/ +static void +fc_shutdown(struct cmd_s *cm) +{ + register vmptr_t vp; + + vp = vm_physmap(FECOM_SWIT0); /* Find loc for data-switch 0 sim */ + op10m_seto(*vp); /* Set word to ones */ + fc_cont(cm); /* Resume CPU, let OS do rest */ +} + +/* FC_HALT - Halts PDP-10 violently. +** Not needed until true threads/subprocess version exists, since +** currently any interaction with KLH10 commands means the 10 is +** already stopped! +*/ +static void +fc_halt(struct cmd_s *cm) +{ +} + + +/* Various commands primarily for debugging */ + +/* FC_ZERO - Clears first 256K of physical memory +*/ +static void +fc_zero(struct cmd_s *cm) +{ + memset((char *)vm_physmap(0), 0, sizeof(w10_t)*(H10MASK+1)); /* Zap! */ + printf("OK\n"); +} + +/* FC_TRACE - Toggles execution tracing +*/ +static void +fc_trace(struct cmd_s *cm) +{ + if (cpu.mr_dotrace) { + cpu.mr_dotrace = 0; + printf("Tracing now off\n"); + } else { + cpu.mr_dotrace = TRUE; + printf("Tracing now ON\n"); + } +} + +#if 0 /* Not bound to a command now, use SET. */ + +/* FC_DEBUG - Toggles general debug flag +*/ +static void +fc_debug(void) +{ + if (cpu.mr_debug) { + cpu.mr_debug = 0; + printf("Debug now off\n"); + } else { + cpu.mr_debug = TRUE; + printf("Debug now ON\n"); + } +} +#endif + +/* FC_STEP - Single-steps by one instruction +*/ +static void +fc_step(struct cmd_s *cm) +{ + putchar('\n'); + cpu.mr_1step = 1; + aprcont(0, 0); + nextinsprint(stdout, PINSTR_OPS); +} + + +/* FC_BKPT - Sets location to stop at. 0 clears. +*/ +static void +fc_bkpt(struct cmd_s *cm) +{ + vaddr_t loc; + enum fevmmode mode; + char *sloc = cm->cmd_arglin; + + if (sloc && *sloc) { + if (!addrparse(sloc, &loc, &mode)) { + printf("?Bad address\n"); + return; + } else if (mode != FEVM_DFLT && mode != FEVM_CUR) { + printf("?Bad address mode - only Current allowed\n"); + return; + } else if va_isglobal(loc) { + printf("?Bad address - only local allowed\n"); + return; + } + + cpu.mr_bkpt = loc; /* Mode will always be current */ + } +} + + +/* FC_PROC - Proceeds for N instructions. +*/ +static void +fc_proc(struct cmd_s *cm) +{ + char *snum = cm->cmd_arglin; + long n = 1; + + if (snum && *snum) + if (!s_tonum(snum, &n)) { + printf("?Bad count syntax\n"); + return; + } + if (n <= 0) { + printf("?Bad proceed count\n"); + return; + } + cpu.mr_1step = n; + aprcont(0, 0); + putchar('\n'); + nextinsprint(stdout, PINSTR_OPS); +} + +/* FC_EXNEXT - Examine next word +** FC_EXPREV - Previous word +*/ +static void +fc_exnext(struct cmd_s *cm) +{ + va_inc(ddt_cloc); + fc_exa((struct cmd_s *)NULL); +} + +static void +fc_exprev(struct cmd_s *cm) +{ + va_dec(ddt_cloc); + fc_exa((struct cmd_s *)NULL); +} + +/* FC_EXA - Examine PDP-10 word +*/ +static void +fc_exa(struct cmd_s *cm) +{ + vaddr_t loc; + enum fevmmode mode; + register vmptr_t vp; + char *sloc; + + sloc = (cm ? cm->cmd_arglin : NULL); + + if (sloc && *sloc) { + if (!addrparse(sloc, &loc, &mode)) { + printf("?Bad address\n"); + return; + } else if (mode != FEVM_DFLT) + ddt_clmode = mode; /* Set new mode if not default */ + ddt_cloc = loc; + } + + putchar(' '); + addrprint(stdout, ddt_cloc, ddt_clmode); + putchar('/'); + putchar(' '); + + if (vp = fevm_xmap(ddt_cloc, ddt_clmode)) { + ddt_val = vm_pget(vp); + wd1print(stdout, ddt_val); + if (LHGET(ddt_val) & 0777000) { /* Opcode exists? */ + putchar('\t'); + pinstr(stdout, ddt_val, 0, ddt_cloc/*unused*/); + } + } else + fprintf(stdout, "\?\?"); + + putchar('\n'); +} + +/* FC_DEP - Deposit PDP-10 word +*/ +static void +fc_dep(struct cmd_s *cm) +{ + w10_t wd; + vaddr_t loc; + register vmptr_t vp; + enum fevmmode mode; + char *sloc, *sval; + + if (!cmdargs_two(cm, &sloc, &sval)) + return; + + if (sloc && *sloc) { + if (!addrparse(sloc, &loc, &mode)) { + printf("?Bad address\n"); + return; + } else if (mode != FEVM_DFLT) + ddt_clmode = mode; /* Set new mode if not default */ + ddt_cloc = loc; + } + + if (!sval || !*sval || !s_towd(sval, &wd)) { + printf("?Bad word syntax\n"); + return; + } + ddt_val = wd; + + if (vp = fevm_xmap(ddt_cloc, ddt_clmode)) + vm_pset(vp, ddt_val); + else { + printf("?Cannot map address "); + addrprint(stdout, ddt_cloc, ddt_clmode); + printf(" - value not deposited\n"); + } +} + +static vmptr_t +fevm_map(paddr_t pa) +{ + if ((pa & ~AC_MASK)==0) + return &cpu.acblk.cur[pa]; /* Use currently active AC block */ + + /* For now, assume physical memory mapping. + ** Later, can use current page map and report error if any failures. + */ + return vm_physmap(pa & H10MASK); +} + +vmptr_t +fevm_xmap(vaddr_t e, enum fevmmode mode) +{ + register pment_t *map; + register acptr_t acp; + register int acb; + + switch (mode) { + default: + case FEVM_CUR: acp = cpu.acblk.cur; map = cpu.vmap.cur; break; + case FEVM_PHYS: acp = cpu.acblk.cur; map = pr_pmap; break; + case FEVM_USER: acp = cpu.acblk.cur; map = cpu.pr_umap; break; + case FEVM_EXEC: acp = cpu.acblk.cur; map = cpu.pr_emap; break; + case FEVM_ACB: + /* Ensure AC block # in LH is 0-7 and AC # in RH is 0-017 inclusive */ + acb = va_lh(e); + if ((acb & ~07) || (va_insect(e) & ~(h10_t)AC_MASK)) + return NULL; /* Ugh, one of them out of range */ + return &cpu.acblks[acb][va_ac(e)]; /* Use AC in right block */ + } + return vm_xtrymap(e, VMF_READ, acp, map); +} + +static int +fevm_tryeacalc(register w10_t *wp, + register acptr_t acp, + register pment_t *map, + int indcnt) /* Indirection level count */ +{ + register h10_t tmp; + register vmptr_t vp; + +#if KLH10_EXTADR +/* + * ERROR * Need to revise this for XA +*/ +#endif + for (;;) { + if ((tmp = LHGET(*wp)) & IW_X) { /* Indexing? */ + register w10_t wea; + wea = ac_xget(tmp & IW_X, acp); /* Get c(X) */ + LHSET(*wp, LHGET(wea)); + RHSET(*wp, (RHGET(*wp)+RHGET(wea))&H10MASK); /* Add previous Y */ + } + if (!(tmp & IW_I)) /* Indirection? */ + return TRUE; /* Nope, return now! */ + + /* Handle indirection */ + if (--indcnt < 0) + return FALSE; + if ((vp = vm_xtrymap(RHGET(*wp), VMF_READ, acp, map)) == NULL) + return FALSE; + *wp = vm_pget(vp); + } +} + +/* FC_VIEW - Show PDP-10 status summary +*/ +static void +fc_view(struct cmd_s *cm) +{ + printf("KN10 status: %s", (cpu.mr_usrmode ? "USER" : "EXEC")); + if (cpu.mr_inpxct) printf(" PXCT-%o", cpu.mr_inpxct); + if (cpu.mr_intrap) printf(" TRAP-%o", cpu.mr_intrap); + if (cpu.mr_injrstf) printf(" JRSTF"); /* Either on or off */ + putchar('\n'); + +#if KLH10_JPC + printf(" PC: %lo [JPC: %lo UJPC: %lo EJPC: %lo]\n", + (long)PC_30, + (long)cpu.mr_jpc, (long)cpu.mr_ujpc, (long)cpu.mr_ejpc); +#if KLH10_ITS_JPC + printf(" ITSPAGER UJPC: %lo EJPC: %lo\n", + (long)cpu.pag.pr_ujpc, (long)cpu.pag.pr_ejpc); +#endif +#endif /* KLH10_JPC */ + printf(" Flags: %lo\n", (long)cpu.mr_pcflags); + + nextinsprint(stdout, PINSTR_OPS); +} + +/* FE_TRACEPRINT called from APR loop if tracing and about to execute +** an instruction. +*/ + +void +fe_traceprint(register w10_t instr, + vaddr_t e) +{ + pishow(stdout); + pcfshow(stdout, cpu.mr_pcflags); + printf("%lo: ", (long)PC_30); + + pinstr(stdout, instr, PINSTR_OPS|PINSTR_EA, e); + putc('\r', stdout); + putc('\n', stdout); +} + +void +fe_begpcfdbg(FILE *f) +{ + putc('[', f); + pishow(f); + pcfshow(f, cpu.mr_pcflags); + fprintf(f,"%lo: => ", (long) PC_30); +} + +void +fe_endpcfdbg(FILE *f) +{ + pishow(f); + pcfshow(f, cpu.mr_pcflags); + fprintf(f,"%lo:]\r\n", (long) PC_30); +} + +void +pishow(FILE *f) /* Show PI status */ +{ + register int lev, num; + if (cpu.pi.pilev_pip) { + num = pilev_nums[cpu.pi.pilev_pip]; /* Find level # */ + fprintf(f, "PI%o", num); + lev = cpu.pi.pilev_pip & ~pilev_bits[num]; /* And remaining PIPs */ + if (lev) { + fputc('[', f); + while (lev) { + num = pilev_nums[lev]; /* Find level # */ + fprintf(f, "%o", num); + lev &= ~pilev_bits[num]; + } + putc(']', f); + } + putc(' ', f); + } +} + +struct { h10_t flag; char ch; } flgtab[] = { + { PCF_ARO, 'O'}, /* Arithmetic Overflow (or Prev Ctxt Public) */ + { PCF_CR0, 'C'}, /* Carry 0 - Carry out of bit 0 */ + { PCF_CR1, 'c'}, /* Carry 1 - Carry out of bit 1 */ + { PCF_FOV, 'F'}, /* Floating Overflow */ + { PCF_FPD, '1'}, /* First Part Done */ + { PCF_USR, 'U'}, /* User Mode */ + { PCF_UIO, 'I'}, /* User In-Out (or Prev Ctxt User) */ + { PCF_PUB, 'P'}, /* Public Mode */ + { PCF_AFI, 'A'}, /* Addr Failure Inhibit */ +#if 0 /* Special-cased */ + { PCF_TR2, 'x'}, /* Trap 2 (PDL overflow) */ + { PCF_TR1, 'x'}, /* Trap 1 (Arith overflow) */ +#endif + { PCF_FXU, 'f'}, /* Floating Exponent Underflow */ + { PCF_DIV, 'D'} /* No Divide */ +}; + +void /* Show PC Flag status */ +pcfshow(FILE *f, + register h10_t flags) +{ + register int i; + + if (flags & (PCF_TR1|PCF_TR2)) { + fprintf(f, "TRAP%o ", (int)FLDGET((uint32)flags, (PCF_TR1|PCF_TR2))); + flags &= ~(PCF_TR1|PCF_TR2); + } + for (i = 0; flags && i < (sizeof(flgtab)/sizeof(flgtab[0])); ++i) + if (flags & flgtab[i].flag) { /* Found it? */ + flags &= ~flgtab[i].flag; /* Turn off */ + putc(flgtab[i].ch, f); + } + if (flags) { /* If any flags still left, */ + fprintf(f, "[PCF: %#lo]", (long)flags); /* show those too */ + } + putc(' ', f); +} + +static void +nextinsprint(FILE *f, + int argf) +{ + w10_t w; + vmptr_t vp; + vaddr_t e; + + fprintf(f, "Next: "); + pishow(f); + pcfshow(f, cpu.mr_pcflags); + fprintf(f, " %lo/ ", (long)PC_30); + + e = PC_VADDR; + if (!(vp = fevm_xmap(e, FEVM_CUR))) { + fprintf(f, " \?\?\n"); + return; + } + w = vm_pget(vp); + if (LHGET(w) & 0777000) { + pinstr(f, w, argf, e/*unused*/); + } else + wd2print(f, w); + fprintf(f, "\n"); +} + + +/* Print address as symbolic if possible. +** This function isn't called by anything yet. +*/ +static void +easymprint(FILE *f, + register vaddr_t e) +{ + + /* Take care of any randomly set bits in LH */ + if (va_lh(e)) fprintf(f, "%lo,,", (long)va_lh(e)); + + /* Now attempt to look up RH value as symbol? */ + + + fprintf(f, "%lo", (long)va_insect(e)); /* Punt for now */ +} + +static void +wd1print(FILE *f, + w10_t w) +{ + if (LHGET(w)) fprintf(f, "%lo,,", (long)LHGET(w)); + fprintf(f, "%lo", (long)RHGET(w)); +} + +static void +wd2print(FILE *f, + w10_t w) +{ + fprintf(f, "%lo,,%lo", (long)LHGET(w), (long)RHGET(w)); +} + + +static void +addrprint(FILE *f, + vaddr_t vloc, + enum fevmmode mode) +{ + register pment_t *map = cpu.vmap.cur; + register int ch; + + switch (mode) { + default: + case FEVM_CUR: ch = 0; break; + case FEVM_PHYS: ch = (map == pr_pmap) ? 0 : 'P'; break; + case FEVM_USER: ch = (map == cpu.pr_umap) ? 0 : 'U'; break; + case FEVM_EXEC: ch = (map == cpu.pr_emap) ? 0 : 'E'; break; + case FEVM_ACB: ch = 'A'; break; + } + + if (ch) { + putc(ch, f); + putc(' ', f); + } + if (va_lh(vloc)) { + fprintf(f, "%lo,,", (long)va_lh(vloc)); + } + fprintf(f, "%#lo", (long)va_insect(vloc)); +} + +static char * +strf6(char **acp, + register w10_t w) +{ + register int i = 6; + char *rp = *acp; + register char *cp = rp; + + while (--i >= 0) { + if (!LHGET(w) && !RHGET(w)) break; + w = op10rot(w, 6); /* Rotate high 6 bits to low 6 */ + *cp++ = (RHGET(w) & 077) + 040; + RHSET(w, RHGET(w) & ~077); + } + *cp++ = 0; /* Note moves past the nul char! */ + *acp = cp; /* Update pointer */ + return rp; /* And return start of original */ +} + +/* ITS disk-format time: */ +#define DFTM_YEAR 0177000 /* LH: 4.7-4.1 Year, mod 100. */ +#define DFTM_MON 0740 /* LH: 3.9-3.6 Month, 1=Jan */ +#define DFTM_DAY 037 /* LH: 3.5-3.1 Day, 1-31 */ +#define DFTM_SECS 0777776 /* RH: 2.9-1.2 Secs in day */ +#define DFTM_HSEC 01 /* RH: 1.1 Half-second resolution */ + +static struct tm * +timefrits(register struct tm *t, + register w10_t w) +{ + t->tm_year = (LHGET(w) & DFTM_YEAR) >> 9; + t->tm_mon = ((LHGET(w) & DFTM_MON) >> 5) - 1; + t->tm_mday = (LHGET(w) & DFTM_DAY); + t->tm_hour = RHGET(w) / (60*60*2); + t->tm_min = (RHGET(w) % (60*60*2)) / 60; + t->tm_sec = (RHGET(w) % (60*60*2)) % 60; + return t; +} + +/* FC_LOAD - Load given filename into PDP10 physical memory. +** May use any executable type, stored on disk in any 36-bit format. +*/ +int ld_debug = 0; /* TRUE to show debug info; use SET to change */ + +static void +fc_load(struct cmd_s *cm) +{ + WFILE lwf; + register FILE *f; + int res = 0; + int wft = -1; + char *farg; + + if (!cmdargs_one(cm, &farg)) + return; + + if (!aprhalted()) { + printf("PDP10 still running! Halt or Reset it first.\n"); + return; + } + + /* Open file for reading */ + if (!(f = fopen(farg, "rb"))) + syserr(errno, "Couldn't open load file \"%s\"", farg); + + /* Determine word format to use */ + if (ld_fmt) { + if ((wft = wf_type(ld_fmt)) < 0) + printf("Unknown ld_fmt setting \"%s\", using default.\n", ld_fmt); + } + if (wft < 0) { + if ((wft = wf_type(ld_dfmt)) < 0) + wft = WFT_U36; + } + wf_init(&lwf, wft, f); /* Init WFILE */ + printf("Using word format \"%s\"...\n", wf_typnam(&lwf)); + + ld_inf.ldi_type = LOADT_UNKNOWN; /* Init ld_inf (assume unknown) */ + ld_inf.ldi_debug = ld_debug; + + res = fe_load(&lwf, &ld_inf); + if (!res) { + printf("Load failed for \"%s\".\n", farg); + } else { + printf("Loaded \"%s\":\n", farg); + ddt_loadsa = RHGET(ld_inf.ldi_startwd); /* Set default GO address */ + } + printf("Format: %s\n", ld_inf.ldi_typname); + printf("Data: %d, Symwds: %d, Low: %#lo, High: %#lo, Startaddress: %#lo\n", + ld_inf.ldi_ndata, ld_inf.ldi_nsyms, + (long)ld_inf.ldi_loaddr, (long)ld_inf.ldi_hiaddr, + (long) RHGET(ld_inf.ldi_startwd)); + if (ld_inf.ldi_evlen != -1) { + if (ld_inf.ldi_evlen != ((h10_t)I_JRST<<9)) + printf("Entvec: %#lo wds at %#lo\n", + (long) ld_inf.ldi_evlen, (long) ld_inf.ldi_evloc); + else + printf("\ +Entvec: JRST (120 ST: %#lo, 124 RE: %#lo, 137 VR: %lo,,%lo)\n", + (long) vm_pgetrh(fevm_map(0120)), + (long) vm_pgetrh(fevm_map(0124)), + (long) vm_pgetlh(fevm_map(0137)), + (long) vm_pgetrh(fevm_map(0137))); + } + + if (ld_inf.ldi_aibgot) { + register w10_t *aib = &ld_inf.ldi_asminf[0]; + char line[100]; + char *s = line; + struct tm t; + + timefrits(&t, aib[AIB_TIME]); /* Decompose ITS time */ + printf("\ +Assembled by %s on %04d-%02d-%02d %02d:%02d:%02d from file \"%s:%s;%s %s\"\n", + strf6(&s, aib[AIB_UNAME]), + t.tm_year + 1900, t.tm_mon+1, t.tm_mday, + t.tm_hour, t.tm_min, t.tm_sec, + strf6(&s, aib[AIB_DEV]), + strf6(&s, aib[AIB_DIR]), + strf6(&s, aib[AIB_FN1]), + strf6(&s, aib[AIB_FN2]) ); + } + + fclose(f); +} + +/* FC_DUMP - Dump PDP10 physical memory into given filename. +** Assumes either ITS SBLK or DEC CSAV format, stored on disk using +** a selectable word mode. +*/ +static void +fc_dump(struct cmd_s *cm) +{ + WFILE lwf; + register FILE *f; + int res = 0; + int wft = -1; + char *farg; + + if (!cmdargs_one(cm, &farg)) + return; + if (!aprhalted()) { + printf("PDP10 still running! Halt or Reset it first.\n"); + return; + } + /* Open file for writing */ + if (!(f = fopen(farg, "wb"))) + syserr(errno, "Couldn't open dump file \"%s\"", farg); + + /* Determine word format to use */ + if (ld_fmt) { + if ((wft = wf_type(ld_fmt)) < 0) + printf("Unknown ld_fmt setting \"%s\", using default.\n", ld_fmt); + } + if (wft < 0) { + if ((wft = wf_type(ld_dfmt)) < 0) + wft = WFT_U36; + } + wf_init(&lwf, wft, f); /* Init WFILE */ + printf("Using word format \"%s\"\n", wf_typnam(&lwf)); + +#if KLH10_SYS_ITS + printf("Using dump format ITS-SBLK\n"); + ld_inf.ldi_type = LOADT_SBLK; +#else + printf("Using dump format DEC-CSAV\n"); + ld_inf.ldi_type = LOADT_DECSAV; +#endif + ld_inf.ldi_debug = TRUE; + ld_inf.ldi_loaddr = 0; + ld_inf.ldi_hiaddr = H10MASK; + + res = fe_dump(&lwf, &ld_inf); + if (!res) { + printf("Dump failed for \"%s\".\n", farg); + } else { + printf("Dumped \"%s\":\n", farg); + } + printf("Format %s, wftype %s\n", ld_inf.ldi_typname, wf_typnam(&lwf)); + printf("%ld words dumped from range %lo to %lo inclusive.\n", + (long) ld_inf.ldi_ndata, (long) ld_inf.ldi_loaddr, + (long) ld_inf.ldi_hiaddr); + + fclose(f); +} + + +/* Instruction printing routines */ + +void +pinstr(FILE *f, + register w10_t w, /* Instruction word */ + int flags, /* PINSTR_ flags */ + vaddr_t e) /* E to use, if PINSTR_EA given */ +{ + register int op, ac, x; + register vmptr_t vp; + register int opflg; + struct opdef *opdf; + + op = iw_op(w) & 0777; /* Get op, with some paranoia */ + ac = iw_ac(w); + + opdf = opcptr[op]; /* Get ptr to opcode definition */ + opflg = opdf ? opdf->opflg : 0; /* Find flags */ + if (!opflg) /* Cover up for unflagged instrs */ + opflg = IF_1X1; /* Generic c(AC) & c(E) instr */ + + if (opflg & IF_IO) { /* Special IO-format instr? */ + x = ((op & 077)<<1) | ((ac >> 3)&01); /* Find device */ + op = ac & 07; /* Find IO operation */ + opflg = opcioflg[op]; /* Get new flags */ + fprintf(f, "%s", opcionam[op]); + if (opcdvnam[x]) + fprintf(f, " %s,", opcdvnam[x]); + else + fprintf(f, " %o,", x<<2); /* Special shift to emulate asmblr */ + } else { + if (!opdf || !opdf->opstr) + fprintf(f, "%3o", op); + else { + fprintf(f, "%s", opdf->opstr); + if (opflg & IF_OPN) + fprintf(f, "-%03o", op); + } + + /* If AC field is 0, only print it if AC is used. */ + if (ac || ((opflg & IF_AS) && (opflg&IF_AFMASK) != IF_A0)) + fprintf(f, " %o,", ac); + else putc(' ', f); + } + + /* Now show I,X,Y for instruction. If X is set, don't show a zero Y. */ + if (iw_i(w)) + putc('@', f); + x = iw_x(w); + if (iw_y(w) || !x) /* Do Y only if NZ or no X */ + fprintf(f, "%lo", (long)iw_y(w)); + if (x) + fprintf(f, "(%o)", x); + + /* Now see whether to show any args of instruction -- tricky part. */ + if ((flags & PINSTR_OPS)==0) + return; /* Nope */ + + /* Show AC as operand? */ + if (opflg & IF_AS) { + if (ac || (opflg & IF_AFMASK)) { + fprintf(f, "\t%o/ ", ac); + switch (opflg & IF_AFMASK) { + case IF_A0: + case IF_A1: + wd1print(f, ac_get(ac)); /* Use current AC block */ + break; + case IF_A2: + wd1print(f, ac_get(ac)); /* Use current AC block */ + fputs(" ? ", f); + wd1print(f, ac_get(ac_off(ac,1))); /* Show AC+1 */ + break; + case IF_A4: + wd1print(f, ac_get(ac)); /* Use current AC block */ + fputs(" ? ", f); + wd1print(f, ac_get(ac_off(ac,1))); /* Show AC+1 */ + fputs(" ? ", f); + wd1print(f, ac_get(ac_off(ac,2))); /* Show AC+2 */ + fputs(" ? ", f); + wd1print(f, ac_get(ac_off(ac,3))); /* Show AC+3 */ + break; + } + } + } + + + /* Show E as operand? */ + if (opflg & IF_MFMASK) { + putc('\t', f); /* Space out */ +#if KLH10_EXTADR +/* + * ERROR * Need to revise for XA eacalc? +*/ +#endif + if ((flags & PINSTR_EA)==0) { /* Try computing E if not given */ + flags |= PINSTR_EA; /* Assume will succeed */ + if (LHGET(w) & (IW_I|IW_X)) { + w10_t eawd; + eawd = w; + if (!fevm_tryeacalc(&eawd, cpu.acblk.cur, cpu.vmap.cur, 2)) + flags &= ~PINSTR_EA; /* Oops, didn't succeed */ + else va_lmake(e, 0, RHGET(eawd)); + } else va_lmake(e, 0, RHGET(w)); + } + if ((flags & PINSTR_EA)==0) + fprintf(f, "E = \?\?"); + else switch (opflg & IF_MFMASK) { + case IF_M1: /* Operand is 1 word */ + case IF_M2: /* " 2 words (double) */ + case IF_M4: /* " 4 words (quad) */ + case IF_MIN: /* " instruction at E */ + +#if KLH10_EXTADR +/* + * ERROR * Need to revise for XA eacalc? +*/ +#endif + fprintf(f, "%lo/ ", (long)va_insect(e)); + if (!(vp = fevm_xmap(e, FEVM_CUR))) { + fprintf(f, "\?\?"); + break; + } + w = vm_pget(vp); + switch (opflg & IF_MFMASK) { + case IF_M1: + wd1print(f, w); /* Show word at E */ + break; + case IF_M2: /* Show double at E */ + case IF_M4: + wd1print(f, w); /* Do first word */ + fprintf(f, " ? "); + va_inc(e); /* Point to next word */ + if (vp = fevm_xmap(e, FEVM_CUR)) + wd1print(f, vm_pget(vp)); + else fprintf(f, "-\?\?-"); + break; + case IF_MIN: /* Show instruction at E */ + pinstr(f, w, 0, e); /* "e" just a handy null vaddr_t */ + break; + } + break; + case IF_ME: /* " E (immediate) */ + case IF_MEIO: /* " IO register (Unibus) */ + fprintf(f, "E = %lo", (long)va_insect(e)); + break; + case IF_ME8: /* " signed 8-bit E */ + { paddr_t pa = va_insect(e); + fprintf(f, "E = %lo = %d.", (long)pa, + (int)((pa&0400) ? (pa | ~0377) : (pa & 0377))); + } + break; + case IF_MEF: /* " floating immediate */ + fprintf(f, "E = %lo = (?.?)", (long)va_insect(e)); + break; + } + } + + /* Show any special stuff for instruction? */ + if (opflg & IF_SPEC) { + } +} diff --git a/src/klh10.h b/src/klh10.h new file mode 100644 index 0000000..1222015 --- /dev/null +++ b/src/klh10.h @@ -0,0 +1,388 @@ +/* KLH10.H - General Configuration Definitions +*/ +/* $Id: klh10.h,v 2.4 2001/11/19 10:39:05 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: klh10.h,v $ + * Revision 2.4 2001/11/19 10:39:05 klh + * Bump version: 2.0A + * + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#ifndef KLH10_INCLUDED +#define KLH10_INCLUDED 1 + +#ifndef KLH10_USE_RCSID /* For now, default to always on */ +# define KLH10_USE_RCSID 1 +#endif +#if KLH10_USE_RCSID +# include "rcsid.h" +#endif +#ifdef RCSID + RCSID(klh10_h,"$Id: klh10.h,v 2.4 2001/11/19 10:39:05 klh Exp $") +#endif + +/* Preliminary legalisms (heavy sigh) */ + +#ifndef KLH10_COPYRIGHT +# define KLH10_COPYRIGHT "\ + Copyright © 2001 Kenneth L. Harrenstien -- All Rights Reserved." +#endif +#ifndef KLH10_WARRANTY +# define KLH10_WARRANTY "This program comes \"AS IS\" with ABSOLUTELY NO WARRANTY." +#endif +#ifndef KLH10_VERSION +# define KLH10_VERSION "V2.0A release" +#endif +#ifndef KLH10_CLIENT +# define KLH10_CLIENT "Generic" +#endif + + +/* C environment setup definitions. +*/ + +#include "cenv.h" /* Get CENV_CPU_ and CENV_SYS_ */ + +/* Canonical C true/false values */ +#define TRUE 1 +#define FALSE 0 + +/* For convenience when passing/setting a function pointer to NULL, to + show its nature without specifying the entire prototype. NULL alone + is sufficient in ANSI C. +*/ +#define NULLPROC (NULL) + +/* Compilation switches defining desired emulation target */ + +/* Define CPU type to emulate. +** For now, ignore peculiarities such as Foonly and Systems Concepts +** since they were primarily emulations of some DEC type. +*/ +#ifndef KLH10_CPU_6 /* DEC PDP-6 (Model 166 processor) */ +# define KLH10_CPU_6 0 +#endif +#ifndef KLH10_CPU_KA /* DEC KA10 */ +# define KLH10_CPU_KA 0 +#endif +#ifndef KLH10_CPU_KI /* DEC KI10 */ +# define KLH10_CPU_KI 0 +#endif +#ifndef KLH10_CPU_KS /* DEC KS10 (2020) */ +# define KLH10_CPU_KS 0 +#endif +#ifndef KLH10_CPU_KL0 /* DEC KL10 (single section - KL10A?) */ +# define KLH10_CPU_KL0 0 +#endif +#ifndef KLH10_CPU_KLX /* DEC KL10 (extended KL10B, 0 or non-0 section) */ +# define KLH10_CPU_KLX 0 +#endif +#ifndef KLH10_CPU_KN /* KLH KN10 (placeholder for non-HW features) */ +# define KLH10_CPU_KN 0 +#endif +#ifndef KLH10_CPU_XKL /* XKL XKL-1 (TOAD-1 System), a super-extended KL */ +# define KLH10_CPU_XKL 0 +#endif + +#if !(KLH10_CPU_6|KLH10_CPU_KA|KLH10_CPU_KI \ + |KLH10_CPU_KS|KLH10_CPU_KL0|KLH10_CPU_KLX|KLH10_CPU_KN|KLH10_CPU_XKL) +# undef KLH10_CPU_KS +# define KLH10_CPU_KS 1 /* Use KS10 as default */ +#endif + +#define KLH10_CPU_KL (KLH10_CPU_KL0 || KLH10_CPU_KLX || KLH10_CPU_XKL) + + +/* Define SYSTEM type emulated machine supports. +** Primarily affects paging, but sometimes a few other things. +** These switches are comparable to KS/KL ucode conditionals, since +** each system tends to have its own peculiar variety of ucode (or +** even different hardware, for KA/KI). +*/ +#ifndef KLH10_SYS_ITS /* MIT ITS system */ +# define KLH10_SYS_ITS 0 +#endif +#ifndef KLH10_SYS_WTS /* Stanford WAITS system */ +# define KLH10_SYS_WTS 0 +#endif +#ifndef KLH10_SYS_10X /* BBN TENEX system */ +# define KLH10_SYS_10X 0 +#endif +#ifndef KLH10_SYS_T10 /* DEC TOPS-10 system */ +# define KLH10_SYS_T10 0 +#endif +#ifndef KLH10_SYS_T20 /* DEC TOPS-20 system */ +# define KLH10_SYS_T20 0 +#endif + +#if !(KLH10_SYS_ITS|KLH10_SYS_WTS|KLH10_SYS_10X|KLH10_SYS_T10|KLH10_SYS_T20) +# undef KLH10_SYS_ITS +# define KLH10_SYS_ITS 1 /* Default for now is ITS (yeah!) */ +#endif + +/* Now define additional flags peculiar to each system/CPU, which +** describe the hardware features emulated ("what" as opposed to "how"). +** These could go into kn10*.h config files, where +** * = 3-letter CPU or SYS identifier. +*/ + +/* Select pager to use. +** DEC has had 3 different varieties of memory mgt: +** KA relocation (not emulated) +** KI paging (emulated; also called "Tops-10 paging") +** KL paging (emulated; also called "Tops-20 paging") +** For ITS this depended on the machine. Only the KS is emulated here. +** For TENEX a special BBN pager was used. This is not emulated, +** but would be quite similar to KL paging. +*/ +#ifndef KLH10_PAG_KI +# define KLH10_PAG_KI 0 +#endif +#ifndef KLH10_PAG_KL +# define KLH10_PAG_KL 0 +#endif +#ifndef KLH10_PAG_ITS +# define KLH10_PAG_ITS 0 +#endif + +/* If no paging scheme explicitly selected, pick default */ +#if !(KLH10_PAG_KI | KLH10_PAG_KL | KLH10_PAG_ITS) +# undef KLH10_PAG_KI +# define KLH10_PAG_KI KLH10_SYS_T10 +# undef KLH10_PAG_KL +# define KLH10_PAG_KL KLH10_SYS_T20 +# undef KLH10_PAG_ITS +# define KLH10_PAG_ITS KLH10_SYS_ITS +#endif + +#if KLH10_SYS_ITS +# ifndef KLH10_ITS_JPC +# define KLH10_ITS_JPC 1 /* Include ITS JPC feature */ +# endif +# if KLH10_ITS_JPC +# undef KLH10_JPC +# define KLH10_JPC 1 /* Include general-purpose JPC, for debug */ +# endif +# ifndef KLH10_ITS_1PROC +# define KLH10_ITS_1PROC 1 /* Include ITS 1-proceed feature */ +# endif +#endif + +#ifndef KLH10_MCA25 /* MCA25 KL Cache/Paging Upgrade */ +# define KLH10_MCA25 (KLH10_CPU_KL && KLH10_SYS_T20) +#endif + +#ifndef KLH10_EXTADR /* True to support extended addressing */ +# define KLH10_EXTADR (KLH10_CPU_KLX || KLH10_CPU_XKL) +#endif + +/* Peripheral Devices +** Determine here which ones will be available for use. +** Further configuration is done at runtime. +*/ + +/* KL10 devices (old-style IO bus devices) */ + +#ifndef KLH10_DEV_DTE +# define KLH10_DEV_DTE KLH10_CPU_KL +#endif +#ifndef KLH10_DEV_RH20 +# define KLH10_DEV_RH20 KLH10_CPU_KL +#endif +#ifndef KLH10_DEV_NI20 +# define KLH10_DEV_NI20 KLH10_CPU_KL +#endif + +/* KS10 devices (new-style Unibus devices) */ + +#ifndef KLH10_DEV_RH11 +# define KLH10_DEV_RH11 KLH10_CPU_KS +#endif +#ifndef KLH10_DEV_DZ11 /* KS10 DZ11 also part of basic system? */ +# define KLH10_DEV_DZ11 KLH10_SYS_ITS /* Try just ITS for now */ +#endif +#ifndef KLH10_DEV_LHDH /* KS10 LHDH IMP interface */ +# define KLH10_DEV_LHDH KLH10_SYS_ITS /* Only on ITS for now */ +#endif +#ifndef KLH10_DEV_CH11 /* KS10 CH11 Chaosnet interface? */ +# define KLH10_DEV_CH11 KLH10_SYS_ITS /* Only on ITS for now */ +#endif + +/* Generic controller drive devices - work for either bus */ + +#ifndef KLH10_DEV_RPXX +# define KLH10_DEV_RPXX (KLH10_DEV_RH20 | KLH10_DEV_RH11) +#endif +#ifndef KLH10_DEV_TM03 +# define KLH10_DEV_TM03 (KLH10_DEV_RH20 | KLH10_DEV_RH11) +#endif + +/* Universal devices - currently just one pseudo-dev */ + +#ifndef KLH10_DEV_HOST +# define KLH10_DEV_HOST 1 +#endif + +/* CPU and PI configuration (PI includes IO) +** The parameters defined earlier specify WHAT to emulate; by contrast, +** these specify HOW the emulation should be done. +*/ + +#ifndef KLH10_PCCACHE /* True to include experimental PC cache stuff */ +# define KLH10_PCCACHE 1 +#endif +#ifndef KLH10_JPC /* True to include JPC feature */ +# define KLH10_JPC 1 /* For now, always - helps debug! */ +#endif + +/* MEMORY - Select emulation method +** Sharable memory has pitfalls but is useful for subproc DMA and +** perhaps future SMP implementation. +*/ +#ifndef KLH10_MEM_SHARED /* TRUE to use sharable memory segment */ +# define KLH10_MEM_SHARED 0 +#endif + +/* REAL-TIME CLOCK - Select emulation method +*/ +#ifndef KLH10_RTIME_SYNCH /* Synchronized - use count */ +# define KLH10_RTIME_SYNCH 0 +#endif +#ifndef KLH10_RTIME_OSGET /* OS value used for all references */ +# define KLH10_RTIME_OSGET 0 +#endif +#ifndef KLH10_RTIME_INTRP /* Interrupt-driven (not implemented) */ +# define KLH10_RTIME_INTRP 0 +#endif +#if !(KLH10_RTIME_SYNCH|KLH10_RTIME_OSGET|KLH10_RTIME_INTRP) +# undef KLH10_RTIME_OSGET +# define KLH10_RTIME_OSGET 1 /* Default to asking system */ +#endif + +/* INTERVAL-TIME CLOCK - Select emulation method +*/ +#ifndef KLH10_ITIME_SYNCH /* Synchronized - use count */ +# define KLH10_ITIME_SYNCH 0 +#endif +#ifndef KLH10_ITIME_INTRP /* Interrupt-driven */ +# define KLH10_ITIME_INTRP 0 +#endif +#if !(KLH10_ITIME_SYNCH|KLH10_ITIME_INTRP) +# undef KLH10_ITIME_SYNCH +# define KLH10_ITIME_SYNCH 1 /* Default to synchronous counter */ +#endif + +/* INTERVAL-TIME CLOCK - set default interval in HZ +** This is the actual interval time in HZ that will be enforced +** unless the user explicitly does a "set clk_ithzfix". +** A value of 0 allows the 10 to set it to anything. +** 60 is a good default; 30 may be needed on slower/older hardware. +*/ +#ifndef KLH10_CLK_ITHZFIX +# define KLH10_CLK_ITHZFIX 60 +#endif + +/* QUANTUM COUNTER - Select emulation method (ITS only) +*/ +#ifndef KLH10_QTIME_SYNCH /* Synchronized - use count */ +# define KLH10_QTIME_SYNCH 0 +#endif +#ifndef KLH10_QTIME_OSREAL /* Use OS realtime */ +# define KLH10_QTIME_OSREAL 0 +#endif +#ifndef KLH10_QTIME_OSVIRT /* Use OS virtual (user CPU) time */ +# define KLH10_QTIME_OSVIRT 0 +#endif + +#if KLH10_SYS_ITS /* Only default if ITS */ +# if !(KLH10_QTIME_SYNCH|KLH10_QTIME_OSREAL|KLH10_QTIME_OSVIRT) +# undef KLH10_QTIME_SYNCH +# define KLH10_QTIME_SYNCH 1 /* Default to synchronous counter */ +# endif +#endif + + +/* DEVICE I/O WAKEUP - Select I/O checking method for various devices. +** These parameters select interrupt-driven methods if TRUE; +** otherwise polling is used. +** Future alternatives may use threads or subprocesses. +*/ +#ifndef KLH10_CTYIO_INT /* True to use CTY interrupts */ +# define KLH10_CTYIO_INT 0 +#endif +#ifndef KLH10_IMPIO_INT /* True to use IMP interrupts */ +# define KLH10_IMPIO_INT 0 +#endif +#ifndef KLH10_EVHS_INT /* True to use new event handling scheme */ +# define KLH10_EVHS_INT 0 +#endif + +/* DEVICE SUBPROCESS - Select basic implementation method for various devices +** These parameters select an asynchronous sub-process method if TRUE; +** otherwise a blocking method is used. +** Not all devices will work without subprocesses (e.g. NI20) +** Future alternatives may use threads. +*/ +#ifndef KLH10_DEV_DPNI20 /* True to use dev subproc for NI20 net */ +# define KLH10_DEV_DPNI20 KLH10_DEV_NI20 +#endif +#ifndef KLH10_DEV_DPRPXX /* True to use dev subproc for RPxx disk */ +# define KLH10_DEV_DPRPXX 0 +#endif +#ifndef KLH10_DEV_DPTM03 /* True to use dev subproc for TM03 tape */ +# define KLH10_DEV_DPTM03 0 +#endif + +/* Two different ways to implement IMP subproc */ +#ifndef KLH10_DEV_DPIMP /* True to use dev subproc for IMP (net) */ +# define KLH10_DEV_DPIMP KLH10_DEV_LHDH +#endif +#ifndef KLH10_DEV_SIMP /* True to use pipe subproc for IMP (net) */ +# define KLH10_DEV_SIMP (KLH10_DEV_LHDH && !KLH10_DEV_DPIMP) +#endif + + +#ifndef KLH10_DEV_DP /* True to include DP subproc support */ +# define KLH10_DEV_DP (KLH10_DEV_DPNI20 \ + |KLH10_DEV_DPRPXX|KLH10_DEV_DPTM03|KLH10_DEV_DPIMP) +#endif + +/* Miscellaneous config vars */ + +#ifndef KLH10_INITFILE /* Default initialization command file */ +# define KLH10_INITFILE "klh10.ini" +#endif + +#ifndef KLH10_DEBUG /* TRUE to include debug output code */ +# define KLH10_DEBUG 1 +#endif + +/* Hack for KS T20 CTY output. (see cty_addint() in dvcty.c) +*/ +#ifndef KLH10_CTYIO_ADDINT /* Set 1 to use hack */ +# define KLH10_CTYIO_ADDINT (KLH10_CPU_KS && KLH10_SYS_T20 && KLH10_CTYIO_INT) +#elif KLH10_CTYIO_ADDINT +# if !KLH10_CPU_KS || !KLH10_SYS_T20 /* Unless a KS T20, */ +# undef KLH10_CTYIO_ADDINT /* force this to 0 */ +# define KLH10_CTYIO_ADDINT 0 +# endif +#endif + +#endif /* ifndef KLH10_INCLUDED */ diff --git a/src/klh10s.h b/src/klh10s.h new file mode 100644 index 0000000..578dce3 --- /dev/null +++ b/src/klh10s.h @@ -0,0 +1,302 @@ +/* KLH10S.H - General Configuration String Definitions +*/ +/* $Id: klh10s.h,v 2.4 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: klh10s.h,v $ + * Revision 2.4 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ +/* +** This include file is used only to map the KLH10 compile-time config +** parameters into strings suitable for runtime display. +*/ + +#ifndef KLH10S_INCLUDED +#define KLH10S_INCLUDED 1 + +#ifdef RCSID + RCSID(klh10s_h,"$Id: klh10s.h,v 2.4 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Environment configuration switches. +** These specify the TARGET platform; code assumes no cross-compilation. +** +** CENV_CPU_x = host CPU architecture +** CENV_SYS_x = host OS +*/ +#if CENV_SYS_V7 /* Basic vanilla Unix */ +# define KLH10S_CENV_SYS_ "V7" +#elif CENV_SYS_SUN /* SunOS 4.x */ +# define KLH10S_CENV_SYS_ "SUN" +#elif CENV_SYS_SOLARIS /* SunOS 5.x */ +# define KLH10S_CENV_SYS_ "SOLARIS" +#elif CENV_SYS_NEXT /* NeXT */ +# define KLH10S_CENV_SYS_ "NEXT" +#elif CENV_SYS_MAC /* Apple Mac */ +# define KLH10S_CENV_SYS_ "MAC" +#elif CENV_SYS_BSDI /* 386 BSDI */ +# define KLH10S_CENV_SYS_ "BSDI" +#elif CENV_SYS_NETBSD /* NetBSD */ +# define KLH10S_CENV_SYS_ "NETBSD" +#elif CENV_SYS_FREEBSD /* FreeBSD */ +# define KLH10S_CENV_SYS_ "FREEBSD" +#elif CENV_SYS_OPENBSD /* FreeBSD */ +# define KLH10S_CENV_SYS_ "OPENBSD" +#elif CENV_SYS_LINUX /* Linux */ +# define KLH10S_CENV_SYS_ "LINUX" +#elif CENV_SYS_DECOSF /* DEC OSF/1 */ +# define KLH10S_CENV_SYS_ "DECOSF" +#elif CENV_SYS_MOONMAC /* Special stuff saved for Dave Moon */ +# define KLH10S_CENV_SYS_ "MOONMAC" +#elif CENV_SYS_BSD /* Generic BSD */ +# define KLH10S_CENV_SYS_ "BSD" +#endif + +#if CENV_CPU_ALPHA /* DEC Alpha AXP series */ +# define KLH10S_CENV_CPU_ "ALPHA" +#elif CENV_CPU_ARM /* DEC/Intel ARM series */ +# define KLH10S_CENV_CPU_ "ARM" +#elif CENV_CPU_I386 /* Intel 386/486 */ +# define KLH10S_CENV_CPU_ "I386" +#elif CENV_CPU_M68 /* MC680x0 series */ +# define KLH10S_CENV_CPU_ "M68" +#elif CENV_CPU_PDP10 /* DEC PDP10 series */ +# define KLH10S_CENV_CPU_ "PDP10" +#elif CENV_CPU_PPC /* IBM/Motorola PowerPC series */ +# define KLH10S_CENV_CPU_ "PPC" +#elif CENV_CPU_SPARC /* SUN SPARC series */ +# define KLH10S_CENV_CPU_ "SPARC" +#else +# define KLH10S_CENV_CPU_ "unknown" +#endif + +/* Compilation switches defining desired emulation target */ + +/* Define CPU type to emulate. +*/ +#if KLH10_CPU_6 /* DEC PDP-6 (Model 166 processor) */ +# define KLH10S_CPU_ "PDP-6" +#elif KLH10_CPU_KA /* DEC KA10 */ +# define KLH10S_CPU_ "KA10" +#elif KLH10_CPU_KI /* DEC KI10 */ +# define KLH10S_CPU_ "KI10" +#elif KLH10_CPU_KS /* DEC KS10 (2020) */ +# define KLH10S_CPU_ "KS10" +#elif KLH10_CPU_KL0 /* DEC KL10 (single section - KL10A?) */ +# define KLH10S_CPU_ "KL10-1sect" +#elif KLH10_CPU_KLX /* DEC KL10 (extended KL10B, 0 or non-0 section) */ +# define KLH10S_CPU_ "KL10-extend" +#elif KLH10_CPU_KN /* KLH KN10 (software machine) */ +# define KLH10S_CPU_ "KN10" +#elif KLH10_CPU_XKL /* XKL XKL-1 (TOAD-1) super-extended KL10B */ +# define KLH10S_CPU_ "XKL-1" +#else +# define KLH10S_CPU_ "unknown" +#endif + +/* Define SYSTEM type emulated machine supports. +** Primarily affects paging, but sometimes a few other things. +** These switches are comparable to KS/KL ucode conditionals, since +** each system tends to have its own peculiar variety of ucode (or +** even different hardware, for KA/KI). +*/ +#if KLH10_SYS_ITS /* MIT ITS system */ +# define KLH10S_SYS_ "ITS" +#elif KLH10_SYS_10X /* BBN TENEX system */ +# define KLH10S_SYS_ "10X" +#elif KLH10_SYS_T10 /* DEC TOPS-10 system */ +# define KLH10S_SYS_ "T10" +#elif KLH10_SYS_T20 /* DEC TOPS-20 system */ +# define KLH10S_SYS_ "T20" +#else +# define KLH10S_SYS_ "unknown" +#endif + +/* Select pager +*/ +#if KLH10_PAG_KI +# define KLH10S_PAG_ "KI" +#elif KLH10_PAG_KL +# define KLH10S_PAG_ "KL" +#elif KLH10_PAG_ITS +# define KLH10S_PAG_ "ITS" +#else +# define KLH10S_PAG_ "unknown" +#endif + + +/* CPU and PI configuration (PI includes IO) */ + +/* INTERNAL CLOCK (from KN10CLK) +*/ +#if KLH10_CLKTRG_OSINT +# define KLH10S_CLKTRG_ "OSINT" +#elif KLH10_CLKTRG_COUNT +# define KLH10S_CLKTRG_ "COUNT" +#else +# define KLH10S_CLKTRG_ "unknown" +#endif + +/* REAL-TIME CLOCK - Select emulation method +*/ +#if KLH10_RTIME_SYNCH /* Synchronized - use count */ +# define KLH10S_RTIME_ "SYNCH" +#elif KLH10_RTIME_OSGET /* OS value used for all references */ +# define KLH10S_RTIME_ "OSGET" +#else +# define KLH10S_RTIME_ "unknown" +#endif + +/* INTERVAL-TIME CLOCK - Select emulation method +*/ +#if KLH10_ITIME_SYNCH /* Synchronized - use count */ +# define KLH10S_ITIME_ "SYNCH" +#elif KLH10_ITIME_INTRP /* Interrupt-driven */ +# define KLH10S_ITIME_ "INTRP" +#else +# define KLH10S_ITIME_ "unknown" +#endif + +/* QUANTUM COUNTER - Select emulation method (ITS only) +*/ +#if KLH10_QTIME_SYNCH /* Synchronized - use count */ +# define KLH10S_QTIME_ "SYNCH" +#elif KLH10_QTIME_OSREAL /* Use OS realtime */ +# define KLH10S_QTIME_ "OSREAL" +#elif KLH10_QTIME_OSVIRT /* Use OS virtual (user CPU) time */ +# define KLH10S_QTIME_ "OSVIRT" +#else +# define KLH10S_QTIME_ "none" +#endif + + +/* Hardware features */ +#if KLH10_MCA25 +# define KLH10S_MCA25 " MCA25" +#else +# define KLH10S_MCA25 "" +#endif +#if KLH10_JPC +# define KLH10S_JPC " JPC" +#else +# define KLH10S_JPC "" +#endif + +/* KLH10 features */ +#if KLH10_DEBUG +# define KLH10S_DEBUG " DEBUG" +#else +# define KLH10S_DEBUG "" +#endif +#if KLH10_PCCACHE +# define KLH10S_PCCACHE " PCCACHE" +#else +# define KLH10S_PCCACHE "" +#endif +#if KLH10_CTYIO_INT +# define KLH10S_CTYIO_INT " CTYINT" +#else +# define KLH10S_CTYIO_INT "" +#endif +#if KLH10_IMPIO_INT +# define KLH10S_IMPIO_INT " IMPINT" +#else +# define KLH10S_IMPIO_INT "" +#endif +#if KLH10_EVHS_INT +# define KLH10S_EVHS_INT " EVHINT" +#else +# define KLH10S_EVHS_INT "" +#endif + + +/* Devices included with build +*/ + +#if KLH10_DEV_DTE /* Console */ +# define KLH10S_DEV_DTE " DTE" +#else +# define KLH10S_DEV_DTE "" +#endif + +#if KLH10_DEV_RH20 /* KL10 RH20 */ +# define KLH10S_DEV_RH " RH20" +#elif KLH10_DEV_RH11 /* KS10 RH11 */ +# define KLH10S_DEV_RH " RH11" +#else +# define KLH10S_DEV_RH "" +#endif + + /* Drive units for RH controllers */ +#if KLH10_DEV_RPXX +# if KLH10_DEV_DPRPXX +# define KLH10S_DEV_RPXX " RPXX(DP)" +# else +# define KLH10S_DEV_RPXX " RPXX" +# endif +#else +# define KLH10S_DEV_RPXX "" +#endif + +#if KLH10_DEV_TM03 +# if KLH10_DEV_DPTM03 +# define KLH10S_DEV_TM03 " TM03(DP)" +# else +# define KLH10S_DEV_TM03 " TM03" +# endif +#else +# define KLH10S_DEV_TM03 "" +#endif + +#if KLH10_DEV_NI20 +# if KLH10_DEV_DPNI20 +# define KLH10S_DEV_NI20 " NI20(DP)" +# else +# define KLH10S_DEV_NI20 " NI20" +# endif +#else +# define KLH10S_DEV_NI20 "" +#endif + + /* KS10 stuff */ +#if KLH10_DEV_DZ11 +# define KLH10S_DEV_DZ11 " DZ11" +#else +# define KLH10S_DEV_DZ11 "" +#endif + +#if KLH10_DEV_CH11 +# define KLH10S_DEV_CH11 " CH11" +#else +# define KLH10S_DEV_CH11 "" +#endif + +#if KLH10_DEV_LHDH +# if KLH10_DEV_DPIMP +# define KLH10S_DEV_LHDH " LHDH(DPIMP)" +# elif KLH10_DEV_SIMP +# define KLH10S_DEV_LHDH " LHDH(SIMP)" +# else +# define KLH10S_DEV_LHDH " LHDH" +# endif +#else +# define KLH10S_DEV_LHDH "" +#endif + +#endif /* ifndef KLH10S_INCLUDED */ + diff --git a/src/kn10clk.c b/src/kn10clk.c new file mode 100644 index 0000000..3ac2b72 --- /dev/null +++ b/src/kn10clk.c @@ -0,0 +1,1150 @@ +/* KN10CLK.C - KLH10 internal clock facilities +*/ +/* $Id: kn10clk.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1994, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: kn10clk.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#include "klh10.h" +#include "osdsup.h" +#include "kn10def.h" /* This includes kn10clk.h */ +#include "kn10clk.h" + +#if KLH10_CLKTRG_OSINT +# include "osdsup.h" /* For os_vtimer */ +#endif + +#ifdef RCSID + RCSID(kn10clk_c,"$Id: kn10clk.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +#ifndef KLH10_CLK_MAXTIMERS /* Max # of timer entries */ +# define KLH10_CLK_MAXTIMERS 32 +#endif + +/* + +The KN10CLK code operates on "cticks" which are internal units of a +value that only has meaning within KN10CLK. Time values outside the +code are in units of microseconds. + + CTICK - smallest time unit CLK knows about. A CTICK is considered +to be either an ITICK or SUB-ITICK (see below). +Synch clock: one ctick is one "instruction cycle" (counter increment). +Asynch clock: one ctick is one host-OS timer interrupt. + + ITICK - Interval tick, the interval the 10 OS knows about. +Some multiple of CTICK. If the 10 has no interval in effect, a default +is used in order to continue driving devices (eg 60HZ). + + SUB-ITICK - a CTICK which is smaller than ITICK. If CTICK and ITICK +are the same interval, then SUB-ITICKs don't exist. + + +Synchronous clock: + one ctick == one "instruction cycle" == 1 usec (for now) + Always running, extremely accurate in virtual world, although + uncontrollably random in real time. + +Real-time clock: + one ctick == one host-OS timer interval interrupt == N usec, + where N is variable depending on the setting + of the interval timer. + Some minimum/maximum interval? + Hair when changing interval?? + Simplification: set rate at startup (config), + then assume either on or off. + +Want to avoid frequent re-computations from # usec into # ticks. +Can either: + (1) Bite bullet, recompute each time. + Good: will always work. + Bad: slower each time must recompute. + (2) Provide time-2-tick facility as macro + Bad: cannot change at runtime. + Bad: DLLs screwed if interval changed. + (3) Provide time-2-tick facility as routine, cache result. + Bad: cached value wrong if interval changes. + (4) Register cached values so CLK code can recompute if interval + changes. + Bad: more complex, esp for devices. + +Facility rules: anything that may use CLK must create a timer and remember +its handle, unless timer handler knows when to kill it (eg for one-shot). + +Allow changing or setting interval, given handle. (Allow this from inside + handler? How to tell in handler? Would need state indicator. + Can do, but disallow for now. + How to tell if active (must resched) or quiescent? Same problem. + OK if simply disallow changing while active.) +Allow setting either one-shots or periodics. Clock code will recompute + values (esp periodics) itself when they change. + +Timer event handler can return: + CLKEVH_RET_KILL - to de-register (good for one-shot). + Can forget handle. + CLKEVH_RET_QUIET - to go quiescent (don't resched even if periodic). + CLKEVH_RET_REPEAT - to repeat, using current interval. + CLKEVH_RET_NOP - indicates handler has disposed of timer. + +Or can explicitly destroy timer, given handle. (allow this from inside + handler? yes, if return NOP.) + +CLK will take care of updating all tick values within timers, including +quiescent ones. + +Different clock entry queue lists: + +CTICK QUEUE: (core clock queue) + Doubly-linked list of active clock entries, completely general. + They are linked in the order they should time out, with each + separated from the next by a relative ctick value. + State CLKENT_ST_CTICK. + +MTICK QUEUE: (multiple-ITICK queue) + Doubly-linked list of "MTICK" entries, which have a tick resolution + of ITICK rather than CTICK. + State CLKENT_ST_MTICK. + +ITICK LIST: (every-ITICK) + Doubly-linked list of "ITICK" entries, all of which are executed + every time an ITICK is fired. + State CLKENT_ST_ITICK. + +QUIET LIST: + Doubly-linked list of "quiet" entries, which facility user wants + to keep around to help cut down overhead. + State CLKENT_ST_QUIET, IQUIET, or MQUIET depending on type of + entry (ctick-queue, itick-list, or mtick-queue). + +FREE LIST: + Singly-linked list of free entries. + State CLKENT_ST_FREE. + + +Need to decide: + Should ACTIVE drive ITICK, or other way around? + + ACTIVE->ITICK: + - General, allows full flexibility + - Problem: how to determine current progress towards ITICK + for code that eg reads interval counter? Can't just + do countval-counter cuz those vals are different + depending on current head of ACTIVE queue. + Solution: maintain and update countval each time ACTIVE + goes off. + - Problem: How often should asynch mode trigger OS int? + Want to minimize this (ie encourage use of ITICK). + But if exact time is important...? Punt for now, + have asynch use ITICK. + Setup: + Put clk_itimeout() as entry in active queue, with + period of one interval. + Synch: set CLOCK_POLLED to bump intf_clk/INSBRK. + Asynch: Set os_vtimer to bump intf_clk/INSBRK + intf_clk test in apr_check should call clk_timeout. + + + BIG ISSUE with ACTIVE->ITICK is whether ACTIVE entries should be fired + on the spot, or deferred until apr_check() is called to provide + a synchronization point. + - Probably OK to fire immediately for synch mode cuz can only + happen at places where CLOCKPOLL is explicitly invoked. + - Unclear what to do for asynch interrupts. + + ITICK->ACTIVE: + - Requires ctick == itick for synch model, not just asynch. + - Probably slightly less overhead. + - CPU code invokes clkq processing only if needed. + Setup: + Put clk_timeout() as entry in ITICK queue. + Synch: set CLOCK_POLLED to bump intf_clk/INSBRK. + Asynch: Set os_vtimer to bump intf_clk/INSBRK. + intf_clk test in apr_check should call clk_itimeout. + + +To recap, four possible modes of operation: + +SYNCH_ITICK: + Driven by ITICK. Interval set by 10, or defaults to 60Hz. + CLOCKPOLL() bumps intf_clk, and INSBRK to abort and get to apr_check. + Go to "sync:" below. + +SYNCH_CTICK: + Driven by ACTIVE. + CLOCKPOLL() invokes clk_trigger(). + If immed callouts supported: + - invokes clk_timeout for immed entries. + - If any synch entries, tickles SYNCH (bumps intf_clk & INSBRK) + (note this could be an entry on immed list) + If no immed callouts: + - tickles SYNCH (bumps intf_clk & INSBRK) + Go to "sync:" below. + +OSINT_ITICK: + Same as below, with optimizations to ignore sub-itick checks. + +OSINT_CTICK: + Driven by ACTIVE. Interval set by 10, or defaults to 60Hz. + Optional: use smaller interval, to approximate true CTICKs? + OS signal invokes clk_osint() at given real-time interval. + clk_osint() bumps intf_clk, and INSBRK to abort and get to apr_check. + [Do NOT execute immediate callouts at interrupt level!] + sync: apr_check notices intf_clk and calls clk_synctimeout(). + clk_synctimeout() bumps ACTIVE counter, calls clk_timeout() if needed + (takes care of things with shorter or longer period than ITICK) + Then invokes clk_itimeout(), either via explicit check or via entry + in ACTIVE list. + If sub-iticks not supported, then always invokes clk_itimeout. + +*/ + +/* + Another way of looking at clock features, from viewpoint of facility +user: + +CALLOUT POINT: (feature) + + IMMEDIATE - Callouts invoked immediately (within instr or OSINT handler). + SYNCHRONIZED - delayed until reach between-instr synch point. + +RESOLUTION: (feature) + + ITICK - Restricted to ITICK resolution (1 or N iticks) + SUB-ITICK - Can callout at smaller intervals than ITICK (can also imply + more-precise longer intervals). + +TRIGGER MECHANISM: (implementation) + + COUNT - from CLOCKPOLL() counting down some number of cycle ticks. + OSINT - from clk_osint() invoked by OS interval timer signal. + + +Ideally all of these could be specified independently for each +particular callout, and in theory this is possible. However, in +practice some restrictions make life simpler: + +COUNT mechanism: + Completely flexible. CLOCKPOLL invokes clk_trigger which checks + for immediate sub-itick callouts and invokes them. One of these + can be the SYNCH tickler, or clk_trigger can automatically tickle SYNCH + if any synch callouts are ready. + SYNCH point will invoke both itick and synch-itick queues. + +OSINT mechanism: + - No SUB-ITICKs. Can only do sub-iticks if OSINT interval + is smaller than ITICK, which is tough. Punt this for now. + - No IMMEDIATE callouts. In a single-threaded environment + these would be invoked from the signal handler, which can find + things in weird states. Would have to suppress signals during critical + code sections; too much overhead. + +For debugging purposes, it would be best if COUNT and CLOCK used the same +mechanism. Thus, the initially supported features are: + + + Callout: SYNCHRONIZED + Resolution: ITICK + Trigger: COUNT or OSINT + + +*/ + +/* Convert various units to and from clkval ticks. +** One tick corresponds either to one instruction, or to +** one OS interval interrupt, depending on compile-time configuration. +** Always returns at least one tick, even if arg is zero. +*/ + +#define clk_msec2clk(ms) clk_usec2clk((ms)*1000) + +static clkval_t +clk_usec2clk(int32 usec) +{ + register clkval_t val; +#if KLH10_CLKTRG_COUNT + /* cticks are instr cycles. Speed is defined by # of cycles per + ** virtual msec; to find # of cycles in N usec, we do + ** * + ** which is + ** * ( / 1000) + ** which is rearranged as below to avoid integer roundoff error. + */ + val = (usec * cpu.clk.clk_ipms) / 1000; +#elif KLH10_CLKTRG_OSINT + val = ((usec + cpu.clk.clk_htickusec) / cpu.clk.clk_tickusec); +#endif + return val ? val : 1; +} + +static int32 +clk_clk2usec(clkval_t clk) +{ +#if KLH10_CLKTRG_COUNT + /* Inverse of computation for usec2clk */ + return (clk * 1000) / cpu.clk.clk_ipms; +#elif KLH10_CLKTRG_OSINT + return clk * cpu.clk.clk_tickusec; +#endif +} + + +static clkval_t +clk_usec2tick(int32 usec) +{ + register clkval_t val; + val = ((usec + cpu.clk.clk_hitickusec) / cpu.clk.clk_itickusec); + return val ? val : 1; +} + +/* Auxiliary macros and declarations */ + +/* Remove from any doubly-linked queue */ +#define clk_2ldelete(e) \ + if ((e)->cke_prev->cke_next = (e)->cke_next) \ + (e)->cke_next->cke_prev = (e)->cke_prev + +/* Remove from a clock queue, updating relative ticks */ +#define clk_qdelete(e) \ + if ((e)->cke_prev->cke_next = (e)->cke_next) \ + ((e)->cke_next->cke_ticks += (e)->cke_ticks), \ + (e)->cke_next->cke_prev = (e)->cke_prev + + +/* Clock queue entries (timers) +*/ +static struct clkent clkenttab[KLH10_CLK_MAXTIMERS]; + +static void clk_stinsert(struct clkent *ce); +static void clk_stdelete(struct clkent *ce); +static void clk_qinsert(struct clkent *ce, struct clkent **qh); +static void clk_osint(void); +static void clk_itusset(int32 usec); + + +void +clk_init(void) +{ + register int i = sizeof(clkenttab)/sizeof(clkenttab[0]); + register struct clkent *ce; + + for (ce = clkenttab; --i >= 0; ++ce) { + ce->cke_state = CLKENT_ST_FREE; + ce->cke_next = i ? ce+1 : NULL; + } + + cpu.clk.clk_free = clkenttab; + cpu.clk.clk_ctickq = NULL; + cpu.clk.clk_itickq = NULL; + cpu.clk.clk_itickl = NULL; + cpu.clk.clk_quiet = NULL; + +#if KLH10_CLKTRG_OSINT + /* Currently OSINT mode must always have a default itick interval; + ** no provision for turning timer completely off if nothing needs it. + ** Use 1/30 sec if the compile-time default is "let PDP-10 set it", since + ** we don't really need more resolution internally and the 10 will + ** almost certainly be setting it to 60 or 1000 after booting. + ** + ** Someday try to make this dynamic by determining whether we're + ** running on a fast or slow machine?? + */ + if (cpu.clk.clk_ithzfix = KLH10_CLK_ITHZFIX) { + cpu.clk.clk_ithzcmreq = cpu.clk.clk_ithzfix; + cpu.clk.clk_itusfix = CLK_USECS_PER_SEC / cpu.clk.clk_ithzfix; + } else { + /* PDP-10 will pick it later; use 1/30 til then */ + cpu.clk.clk_ithzcmreq = 30; + cpu.clk.clk_itusfix = 0; + } + + cpu.clk.clk_ithzosreq = 0; /* No OS request yet */ + cpu.clk.clk_ithz = cpu.clk.clk_ithzcmreq; + cpu.clk.clk_itickusec = CLK_USECS_PER_SEC / cpu.clk.clk_ithz; + cpu.clk.clk_hitickusec = cpu.clk.clk_itickusec / 2; + + /* CTICK == ITICK */ + cpu.clk.clk_tickusec = cpu.clk.clk_itickusec; + cpu.clk.clk_htickusec = cpu.clk.clk_hitickusec; + cpu.clk.clk_icntval = 1; + cpu.clk.clk_icnter = 0; + + /* Don't start the OS interval timer right now; clk_resume() will do + ** that for us when the CPU is started. + */ + +#elif KLH10_CLKTRG_COUNT + + cpu.clk.clk_ipms = 1000; /* Default 1:1 instrs:usec */ + cpu.clk.clk_ipmsrq = cpu.clk.clk_ipms; + + /* Set up ctick stuff */ + cpu.clk.clk_counter = cpu.clk.clk_ocnt = CLKVAL_NEVER; + + cpu.clk.clk_itickusec = 0; /* No interval defined yet */ + cpu.clk.clk_icntval = 0; + cpu.clk.clk_icnter = 0; +#endif +} + +/* CLK_SUSPEND and CLK_RESUME +** These are for temporarily halting internal clock interrupts and +** timeout processing. +** Because these are currently only invoked when starting or stopping +** the PDP-10, it's easiest to simply turn the OS timer on or off +** completely. Note this also avoids having a SIGALRM always awaiting +** the CPU when execution is continued! +*/ +void +clk_suspend(void) +{ +#if KLH10_CLKTRG_OSINT + os_vtimer((ossighandler_t *)clk_osint, (uint32)0); +#endif +} +void +clk_resume(void) +{ +#if KLH10_CLKTRG_OSINT + os_vtimer((ossighandler_t *)clk_osint, cpu.clk.clk_itickusec); +#endif +} + +/* CLK_IDLE - Called to go into an OS-dependent process idle state until +** some PDP-10 interrupt happens. +** This is intended to be called as a result of some special instruction +** inserted into the PDP-10 OS null job. +** +** If doing synchronous counting, attempt to translate the remaining count +** into some amount of real time, and sleep on that. +** If doing virtual-time OS interrupts, must determine approx amount of real +** time left, and sleep on that. +** If doing real-time OS interrupts, just pause. +** If can't do anything or figure out what to do, it's OK to just do nothing +** and return. Worst that will happen is the platform spins and wastes +** some CPU. +*/ +void +clk_idle(void) +{ +#if KLH10_CLKTRG_COUNT + uint32 usec; + + /* Figure usec to next synchronized interrupt */ + usec = clk_clk2usec(CLK_CTICKS_UNTIL_ITICK()); + + if (usec <= 100) /* Arbitrary cutoff for now */ + return; /* Not enough time, don't bother */ + + /* Sleep until: + ** timeout of usec (all gone, restart interval) + ** or some other interrupt (some left, adjust interval) + */ + /* KLH: NEEDS IMPLEMENTATION HERE */ + +#elif KLH10_CLKTRG_OSINT + + os_v2rt_idle((ossighandler_t *)clk_osint); + +#endif +} + +/* CLK_OSINT - Interrupt routine invoked on each real-time interval interrupt +*/ +static void +clk_osint(void) +{ + CLK_TRIGGER(); /* For now, macro that triggers synch call */ +} + + +/* CLK_SYNCTIMEOUT - called from synchronization point to invoke clock timeout. +** If no sub-iticks supported, always invokes clk_itimeout functionality. +*/ + +void +clk_synctimeout(void) +{ + register struct clkent *ce, *nce; + + /* If doing sub-itick resolution, this is where the sub-itick callouts + ** should be done. + */ +#if KLH10_CLKRES_SUBITICK +# if KLH10_CLKTRG_COUNT + /* Update cticks thus far by original # of cticks since last + ** setting of counter. + */ + cpu.clk.clk_icnter += cpu.clk.clk_ocnt; +# endif + + if (ce = cpu.clk.clk_ctickq) { + ce->cke_ticks = 0; /* Canonicalize sub-itick counter */ + + do { + nce = ce->cke_next; + switch ((*(ce->cke_rtn))(ce->cke_arg)) { + default: + /* panic?? */ + + case CLKEVH_RET_NOP: /* Do nothing (handler hacked self) */ + break; + + case CLKEVH_RET_KILL: /* Kill, put on freelist */ + if (ce->cke_prev->cke_next = nce) /* Take off queue */ + nce->cke_prev = ce->cke_prev; + ce->cke_state = CLKENT_ST_FREE; /* Add to freelist */ + ce->cke_next = cpu.clk.clk_free; + cpu.clk.clk_free = ce; + break; + + case CLKEVH_RET_QUIET: /* Go quiescent */ + if (ce->cke_prev->cke_next = nce) /* Take off queue */ + nce->cke_prev = ce->cke_prev; + ce->cke_state = CLKENT_ST_QUIET; /* Add to quiet list */ + ce->cke_prev = (struct clkent *)&cpu.clk.clk_quiet; + if (ce->cke_next = cpu.clk.clk_quiet) + ce->cke_next->cke_prev = ce; + cpu.clk.clk_quiet = ce; + break; + + case CLKEVH_RET_REPEAT: /* Put back on queue */ + if (nce) { /* If other entries exist, */ + if (ce->cke_prev->cke_next = nce) /* Take off queue */ + nce->cke_prev = ce->cke_prev; + clk_stinsert(ce); /* Put back */ + } else { + ce->cke_ticks = ce->cke_oticks; + } + break; + + } + } while ((ce = nce) && ce->cke_ticks <= 0); + + cpu.clk.clk_counter = cpu.clk.clk_ocnt + = (ce ? ce->cke_ticks : CLKVAL_NEVER); + } else { + /* No queue entries, need to turn clock off??? */ + /* This actually shouldn't happen, but... */ + cpu.clk.clk_counter = cpu.clk.clk_ocnt = CLKVAL_NEVER; + } + + /* If haven't reached an ITICK boundary yet, return now. + ** Else drop thru to handle ITICK stuff + */ +#endif /* KLH10_CLKRES_SUBITICK */ + +#if KLH10_CLKRES_ITICK +# if KLH10_CLKTRG_COUNT + /* Reset ctick counter to use a full tick */ + cpu.clk.clk_counter = cpu.clk.clk_ocnt = cpu.clk.clk_icntval; + cpu.clk.clk_icnter = 0; +# endif + + /* Now check for special ITICK callouts */ + if (ce = cpu.clk.clk_itickl) { + do { + /* ITICK callouts don't return values and are actually of type void + ** instead of int, hence funct pointer must be cast. + ** These callouts must be flushed + ** explicitly with clk_tmrkill() rather than by returning + ** CLKEVH_RET_KILL. + */ + nce = ce->cke_next; + (*(void (*)(void *))(ce->cke_rtn)) (ce->cke_arg); + } while (ce = nce); + } +#endif /* KLH10_CLKRES_ITICK */ + + /* Now check for callouts of ITICK resolution which aren't ITICKs */ + if ((ce = cpu.clk.clk_itickq) + && --(ce->cke_ticks) <= 0) { + do { + nce = ce->cke_next; + switch ((*(ce->cke_rtn))(ce->cke_arg)) { + default: + /* panic?? */ + + case CLKEVH_RET_NOP: /* Do nothing (handler hacked self) */ + break; + + case CLKEVH_RET_KILL: /* Kill, put on freelist */ + if (ce->cke_prev->cke_next = nce) /* Take off queue */ + nce->cke_prev = ce->cke_prev; + ce->cke_state = CLKENT_ST_FREE; /* Add to freelist */ + ce->cke_next = cpu.clk.clk_free; + cpu.clk.clk_free = ce; + break; + + case CLKEVH_RET_QUIET: /* Go quiescent */ + if (ce->cke_prev->cke_next = nce) /* Take off queue */ + nce->cke_prev = ce->cke_prev; + ce->cke_state = CLKENT_ST_MQUIET; /* Add to quiet list */ + ce->cke_prev = (struct clkent *)&cpu.clk.clk_quiet; + if (ce->cke_next = cpu.clk.clk_quiet) + ce->cke_next->cke_prev = ce; + cpu.clk.clk_quiet = ce; + break; + + case CLKEVH_RET_REPEAT: /* Put back on queue */ + if (nce) { /* If other entries exist, */ + if (ce->cke_prev->cke_next = nce) /* Take off queue */ + nce->cke_prev = ce->cke_prev; + clk_qinsert(ce, &cpu.clk.clk_itickq); /* Put back */ + } else { + ce->cke_ticks = ce->cke_oticks; + } + break; + + } + } while ((ce = nce) && ce->cke_ticks <= 0); + } +} + + +#if 0 /* Now part of clk_synctimeout!! */ + +/* Invoke interval timeout, execute everything on ITICK list. +*/ +void +clk_itimeout() +{ +} +#endif /* 0 */ + + + +/* CLK_IMMTIMEOUT - invoked by OSINT or COUNT when clock-trigger event +** happens, to check for and execute any *immediate* callouts. +** Not yet implemented. +*/ +void +clk_immtimeout(void) +{ +} + + +/* Routines to get a timer (clock callout entry) +** clk_tmrget(rtn, arg, usec) - General-purpose timer (ITICK res) +** clk_ctmrget(rtn, arg, usec) - Sub-itick timer (CTICK res) +** clk_itmrget(rtn, arg) - Permanent ITICK-interval +*/ + +struct clkent * +clk_tmrget(int (*rtn)(void *), void *arg, int32 usec) +{ + register struct clkent *ce; + + if (ce = cpu.clk.clk_free) { + cpu.clk.clk_free = ce->cke_next; + ce->cke_state = CLKENT_ST_MTICK; + ce->cke_rtn = rtn; + ce->cke_arg = arg; + ce->cke_usec = usec; + ce->cke_oticks = clk_usec2tick(usec); /* Convert time to ITICKS */ + clk_qinsert(ce, &cpu.clk.clk_itickq); + } + return ce; +} + +#if KLH10_CLKRES_SUBITICK + +struct clkent * +clk_ctmrget(int (*rtn)(void *), void *arg, int32 usec) +{ + register struct clkent *ce; + + if (ce = cpu.clk.clk_free) { + cpu.clk.clk_free = ce->cke_next; + ce->cke_state = CLKENT_ST_CTICK; + ce->cke_rtn = rtn; + ce->cke_arg = arg; + ce->cke_usec = usec; + ce->cke_oticks = clk_usec2clk(usec); /* Convert time to CTICKS */ + clk_stinsert(ce); /* Insert */ + } + return ce; +} +#endif + +/* As above, but specifically intended to time out every interval tick, and +** returned value of callout routine is ignored. +*/ +struct clkent * +clk_itmrget(void (*rtn)(void *), void *arg) +{ + register struct clkent *ce; + + if (ce = cpu.clk.clk_free) { /* Get a free entry */ + cpu.clk.clk_free = ce->cke_next; + ce->cke_rtn = (int (*)(void *)) rtn; /* Set up its value */ + ce->cke_arg = arg; + + /* Now add to the itick list */ + ce->cke_state = CLKENT_ST_ITICK; + ce->cke_prev = (struct clkent *)&cpu.clk.clk_itickl; + if (ce->cke_next = cpu.clk.clk_itickl) + ce->cke_next->cke_prev = ce; + cpu.clk.clk_itickl = ce; + } + return ce; +} + + +/* Set a timer interval, using quiescent timer */ + +void +clk_tmrset(register struct clkent *ce, int32 usec) +{ + if (ce) switch (ce->cke_state) { + default: + /* Shouldn't happen; panic? */ + return; + + case CLKENT_ST_IQUIET: /* Cannot change interval of these */ + case CLKENT_ST_ITICK: + return; + + case CLKENT_ST_MQUIET: + ce->cke_usec = usec; + ce->cke_oticks = clk_usec2tick(usec); /* ITICK units */ + break; + + case CLKENT_ST_QUIET: + ce->cke_usec = usec; + ce->cke_oticks = clk_usec2clk(usec); /* CTICK units */ + break; + + case CLKENT_ST_CTICK: + clk_stdelete(ce); /* Take off sub-itick queue */ + ce->cke_usec = usec; /* Set new time values */ + ce->cke_oticks = clk_usec2clk(usec); /* Convert to sub-iticks */ + clk_stinsert(ce); /* Insert back into sub-itick queue */ + break; + + case CLKENT_ST_MTICK: + clk_qdelete(ce); /* Take off mtick queue list */ + ce->cke_usec = usec; /* Set new time values */ + ce->cke_oticks = clk_usec2tick(usec); /* Convert to iticks */ + clk_qinsert(ce, &cpu.clk.clk_itickq); /* Insert back into queue */ + break; + } +} + +/* Make a timer quiescent. +*/ +void +clk_tmrquiet(register struct clkent *ce) +{ + if (ce) switch (ce->cke_state) { + default: + /* Shouldn't happen; panic? */ + return; + + case CLKENT_ST_IQUIET: /* Already quiet */ + case CLKENT_ST_MQUIET: + case CLKENT_ST_QUIET: + return; + + case CLKENT_ST_MTICK: /* Active on ITICK queue */ + clk_qdelete(ce); /* Take off its list */ + ce->cke_state = CLKENT_ST_MQUIET; + break; + + case CLKENT_ST_CTICK: /* Active on CTICK queue */ + clk_stdelete(ce); /* Take off its list */ + ce->cke_state = CLKENT_ST_QUIET; + break; + + case CLKENT_ST_ITICK: + clk_2ldelete(ce); /* Take off its list */ + ce->cke_state = CLKENT_ST_IQUIET; + break; + } + + /* Add to quiet list */ + ce->cke_prev = (struct clkent *)&cpu.clk.clk_quiet; + if (ce->cke_next = cpu.clk.clk_quiet) + ce->cke_next->cke_prev = ce; + cpu.clk.clk_quiet = ce; +} + + +/* Activate a quiescent timer. +*/ +void +clk_tmractiv(register struct clkent *ce) +{ + if (ce) switch (ce->cke_state) { + default: + /* Shouldn't happen; panic? */ + return; + + case CLKENT_ST_IQUIET: + clk_2ldelete(ce); /* Take off its list */ + ce->cke_state = CLKENT_ST_ITICK; + + /* Add onto ITICK list */ + ce->cke_prev = (struct clkent *)&cpu.clk.clk_itickl; + if (ce->cke_next = cpu.clk.clk_itickl) + ce->cke_next->cke_prev = ce; + cpu.clk.clk_itickl = ce; + break; + + case CLKENT_ST_MQUIET: + clk_2ldelete(ce); /* Take off its list */ + ce->cke_state = CLKENT_ST_MTICK; + clk_qinsert(ce, &cpu.clk.clk_itickq); /* add onto ITICK queue */ + break; + + case CLKENT_ST_QUIET: + clk_2ldelete(ce); /* Take off its list */ + ce->cke_state = CLKENT_ST_CTICK; + clk_qinsert(ce, &cpu.clk.clk_ctickq); /* add onto CTICK queue */ + break; + + case CLKENT_ST_CTICK: /* Already active */ + case CLKENT_ST_MTICK: + case CLKENT_ST_ITICK: + break; + } +} + + +/* Delete timer given handle */ + +void +clk_tmrkill(register struct clkent *ce) +{ + + if (!ce) + return; + switch (ce->cke_state) { + case CLKENT_ST_FREE: + return; + + case CLKENT_ST_CTICK: + clk_stdelete(ce); + break; + + case CLKENT_ST_MTICK: + clk_qdelete(ce); /* Take off clock queue */ + break; + + case CLKENT_ST_QUIET: + case CLKENT_ST_IQUIET: + case CLKENT_ST_MQUIET: + case CLKENT_ST_ITICK: + clk_2ldelete(ce); /* Take off its list */ + break; + } + + /* Put entry on freelist */ + ce->cke_state = CLKENT_ST_FREE; + ce->cke_next = cpu.clk.clk_free; + cpu.clk.clk_free = ce; +} + + +/* Routines to manage active clock queue */ + + +/* Insert entry into appropriate place in active queue. + * NOTE! For speed this uses a type punning hack - it works + * as long as cke_next is the FIRST element in a clkent. + */ +static void +clk_qinsert(register struct clkent *ce, + struct clkent **qh) +{ + register struct clkent *pce = (struct clkent *)qh; + register struct clkent *nce; + register clkval_t tim = ce->cke_oticks; + + for (nce = pce; nce = nce->cke_next; pce = nce) { + if ((tim -= nce->cke_ticks) < 0) { + /* Win, must put entry ahead of this one */ + tim += nce->cke_ticks; /* Restore time */ + nce->cke_ticks -= tim; /* and adjust next entry */ + break; + } + } + + /* Found place, put entry between PCE and NCE */ + ce->cke_ticks = tim; + ce->cke_prev = pce; + pce->cke_next = ce; + if (ce->cke_next = nce) + nce->cke_prev = ce; +} + + + +/* Insert entry into sub-itick queue. +** Needs special hackery to update separate sub-itick counter. +*/ +static void +clk_stinsert(register struct clkent *ce) +{ + + /* Canonicalize sub-tick counter state before trying to insert */ + if (cpu.clk.clk_ctickq) { + cpu.clk.clk_ctickq->cke_ticks = cpu.clk.clk_counter; + clk_qinsert(ce, &cpu.clk.clk_ctickq); + } else { + /* Optimize case where queue was empty */ + ce->cke_prev = (struct clkent *)&cpu.clk.clk_ctickq; + ce->cke_next = NULL; + cpu.clk.clk_ctickq = ce; + } + cpu.clk.clk_counter = cpu.clk.clk_ocnt /* Update counter state */ + = cpu.clk.clk_ctickq->cke_ticks; +} + +/* Delete entry from sub-itick queue. +** Needs special hackery to update separate sub-itick counter. +*/ +static void +clk_stdelete(register struct clkent *ce) +{ + + if (ce == cpu.clk.clk_ctickq) { + cpu.clk.clk_ctickq->cke_ticks = cpu.clk.clk_counter; + clk_qdelete(ce); + if (cpu.clk.clk_ctickq) + cpu.clk.clk_counter = cpu.clk.clk_ocnt + = cpu.clk.clk_ctickq->cke_ticks; + } else + /* Not at head, can just do simple call */ + clk_qdelete(ce); /* Take off clock queue */ +} + + +#if 0 /* Not used yet (maybe never) */ + +/* Adjust all tick counters. +** Something's changed in the way ticks are computed, so recompute +** them all. +** "usec" is the new number of usec per ctick. +*/ +static void +clk_ctickset(int32 usec) +{ + register struct clkent *ce; + register clkval_t clkval; + register int32 cntusec; + + if (usec == cpu.clk.clk_tickusec) + return; /* No change, no op */ + + /* Do active clock queue. First entry needs + ** special-casing since actual tick counter is in clk struct. + */ + if (ce = cpu.clk.clk_ctickq) { + + /* Reverse-compute current countdown back to usec, so can then + ** convert to new value in ticks. + */ + cntusec = cpu.clk.clk_counter * cpu.clk.clk_tickusec; + cpu.clk.clk_tickusec = usec; /* OK to set new value now */ + cpu.clk.clk_htickusec = usec>>2; /* Get half-tick for round */ + cpu.clk.clk_counter = clkval = clk_usec2clk(cntusec); + cpu.clk.clk_ocnt = clkval; + + ce->cke_ticks = clkval; /* Update 1st entry */ + ce->cke_oticks = clk_usec2clk(ce->cke_usec); + + /* For each remaining entry, convert ticks, then update + ** relative count. + */ + while (ce = ce->cke_next) { + ce->cke_oticks = clk_usec2clk(ce->cke_usec); + if ((ce->cke_ticks = ce->cke_oticks - clkval) < 0) + ce->cke_ticks = 0; + clkval += ce->cke_ticks; + } + + } else { + cpu.clk.clk_tickusec = usec; /* OK to set new value now */ + cpu.clk.clk_htickusec = usec>>2; /* Get half-tick for round */ + cpu.clk.clk_counter = cpu.clk.clk_ocnt = CLKVAL_NEVER; + } + + /* Finally do quiescent list. */ + for (ce = cpu.clk.clk_quiet; ce; ce = ce->cke_next) { + switch (ce->cke_state) { + case CLKENT_ST_QUIET: + ce->cke_oticks = clk_usec2clk(ce->cke_usec); + break; + case CLKENT_ST_MQUIET: + ce->cke_oticks = clk_usec2tick(ce->cke_usec); + break; + } + } +} + +#endif /* 0 */ + + +/* CLK_ITHZSET - Set ITICK interval, using HZ instead of usec. +** This function is currently invoked only by user command when +** either the current or fixed interval in HZ is changed. +** "hz" is the desired number of iticks per second. +** Perhaps 0 could turn clock off (similar to clk_suspend but +** in a different way). +*/ +void +clk_ithzset(int hz) +{ + int32 usec; + + /* Sanity check, don't go too high or low */ + if (cpu.clk.clk_ithzfix) { /* Force to this if set */ + hz = cpu.clk.clk_ithzfix; + } else if (hz <= 0) { + hz = 1; + } else if (hz > 1000) { /* 1 ms is max ever */ + hz = 1000; + } + usec = (CLK_USECS_PER_SEC + (hz/2)) / hz; /* Compute interval */ + + /* Remember or clear fixed usec */ + cpu.clk.clk_itusfix = (cpu.clk.clk_ithzfix ? usec : 0); + + if (usec != cpu.clk.clk_itickusec) /* If changing interval, */ + clk_itusset(usec); /* Do it */ +} + +/* CLK_ITICKSET - Set ITICK interval. +** This is the function called by any PDP-10 OS instructions. +** +** "usec" is the new number of usec per itick. +*/ +void +clk_itickset(int32 usec) +{ + cpu.clk.clk_ithzosreq = CLK_USECS_PER_SEC / usec; /* Remember last req */ + + if (!cpu.clk.clk_ithzfix /* Change allowed? */ + && (usec != cpu.clk.clk_itickusec)) /* And new setting? */ + clk_itusset(usec); /* Do it */ +} + +/* CLK_ITUSSET - Set ITICK interval in usec. +** +** "usec" is the new number of usec per itick. +*/ +static void +clk_itusset(int32 usec) +{ + register struct clkent *ce; + register clkval_t clkval; + +#if KLH10_CLKRES_ITICK +# if KLH10_CLKTRG_COUNT + register int32 cntusec; + + /* Preserve current interval countdown by up-converting to usec */ + cntusec = clk_clk2usec(CLK_CTICKS_SINCE_ITICK()); +# endif + + /* Set new itick interval */ + cpu.clk.clk_itickusec = usec; /* Define new interval */ + cpu.clk.clk_hitickusec = usec >> 1; + cpu.clk.clk_icntval = clk_usec2clk(usec); /* Find in # cticks */ + cpu.clk.clk_ithz = cpu.clk.clk_ithzcmreq /* Find in HZ */ + = (CLK_USECS_PER_SEC + (usec/2)) / usec; +#endif /* KLH10_CLKRES_ITICK */ + + /* Update itick clock queue. + ** For each entry, convert ticks, then update relative count. + */ + clkval = 0; + for (ce = cpu.clk.clk_itickq; ce; ce = ce->cke_next) { + ce->cke_oticks = clk_usec2tick(ce->cke_usec); + if ((ce->cke_ticks = ce->cke_oticks - clkval) < 0) + ce->cke_ticks = 0; + clkval += ce->cke_ticks; + } + + /* Update quiescent list - do all entries that might go back + ** on itick queue later. + */ + for (ce = cpu.clk.clk_quiet; ce; ce = ce->cke_next) { + switch (ce->cke_state) { + case CLKENT_ST_MQUIET: + ce->cke_oticks = clk_usec2tick(ce->cke_usec); + break; + } + } + +#if KLH10_CLKRES_ITICK +# if KLH10_CLKTRG_OSINT + /* Tell OS about new interval! */ + os_vtimer((ossighandler_t *)clk_osint, cpu.clk.clk_itickusec); + +# elif KLH10_CLKTRG_COUNT + /* Now restore state of interval countdown */ + cpu.clk.clk_icnter = clk_usec2clk(cntusec); /* Find # cticks done so far */ + cpu.clk.clk_ocnt = cpu.clk.clk_icntval; /* Assume started with itick */ + cpu.clk.clk_counter = + cpu.clk.clk_ocnt - cpu.clk.clk_icnter; +# endif +#endif /* KLH10_CLKRES_ITICK */ +} + +#if KLH10_CLKTRG_COUNT + +/* Set virtual clock speed - instrs per msec. +** This defines the ratio of virtual usec to instruction cycles. +** Only meaningful when using KLH10_CLKTRG_COUNT. +** Default value of 1000 is a 1:1 ratio. +*/ +void +clk_ipmsset(int32 ipms) +{ + register int32 cntusec; + + cpu.clk.clk_ipmsrq = ipms; + if (ipms == cpu.clk.clk_ipms) + return; /* No change, no op */ + + /* Preserve current interval countdown by up-converting to usec */ + cntusec = clk_clk2usec(CLK_CTICKS_SINCE_ITICK()); + + /* Set new ctick definition */ + cpu.clk.clk_ipms = ipms; + + /* Set new itick interval, based on new ctick definition. + ** Note time in terms of virtual usec does + ** not change, just our definition of what a usec is. + */ + cpu.clk.clk_icntval = clk_usec2clk(cpu.clk.clk_itickusec); + + + /* Now restore state of interval countdown */ + cpu.clk.clk_icnter = clk_usec2clk(cntusec); /* Find # cticks done so far */ + cpu.clk.clk_ocnt = cpu.clk.clk_icntval; /* Assume started with itick */ + cpu.clk.clk_counter = + cpu.clk.clk_ocnt - cpu.clk.clk_icnter; +} + +#endif /* KLH10_CLKTRG_COUNT */ + diff --git a/src/kn10clk.h b/src/kn10clk.h new file mode 100644 index 0000000..853b5e0 --- /dev/null +++ b/src/kn10clk.h @@ -0,0 +1,240 @@ +/* KLH10.H - KLH10 internal clock facility definitions +*/ +/* $Id: kn10clk.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1994, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: kn10clk.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#ifndef KN10CLK_INCLUDED +#define KN10CLK_INCLUDED 1 + +#ifdef RCSID + RCSID(kn10clk_h,"$Id: kn10clk.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Determine desired clock features and implementation mechanism +** for (a) Triggering, (b) Resolution, and (c) Callout Point. +*/ + +/* Triggering mechanism (implementation), one of: +** COUNT - Counter bumped by CLOCKPOLL once per executed instruction. +** OSINT - host-OS interval timer interrupt. +*/ +#ifndef KLH10_CLKTRG_OSINT +# define KLH10_CLKTRG_OSINT !IFCLOCKED /* Use OS ints for iticks */ +#endif +#define KLH10_CLKTRG_COUNT !KLH10_CLKTRG_OSINT /* Use counter for iticks */ + +/* Clock resolution (feature), one of: +** ITICK - Same as PDP-10 monitor's interval time. Callouts restricted +** to ITICK resolution. +** SUBITICK - Some finer value; can callout at smaller intervals than +** ITICK (can also imply more precise longer intervals). +** Not supported yet. +*/ +#define KLH10_CLKRES_ITICK 1 /* Resolution is 1 itick */ +#define KLH10_CLKRES_SUBITICK !KLH10_CLKRES_ITICK /* Sub-itick res */ + +/* Callout point (feature), one of: +** SYNCH - callouts delayed until reach between-instr synch point. +** IMMED - callouts invoked immediately (even within instr or OSINT +** handler). Not supported yet. +*/ +#define KLH10_CLKXCT_SYNCH 1 /* Synchronized callouts */ +#define KLH10_CLKXCT_IMMED !KLH10_CLKXCT_SYNCH /* Can do immediate callouts */ + + +/* Typedef for scalar variable holding # ticks */ +typedef int32 clkval_t; + +#define CLKVAL_NEVER ((clkval_t)((~0UL)>>1)) /* Max pos # */ + +enum clksta { /* Clock queue entry states */ + CLKENT_ST_FREE, /* On freelist */ + CLKENT_ST_CTICK, /* on active CTICK queue (sub-ITICK) */ + CLKENT_ST_ITICK, /* on active ITICK queue */ + CLKENT_ST_MTICK, /* on active MTICK queue (multi-ITICK) */ + CLKENT_ST_QUIET, /* on quiescent CTICK q (sub-ITICK) */ + CLKENT_ST_IQUIET, /* on quiescent ITICK q */ + CLKENT_ST_MQUIET /* on quiescent MTICK q (multi-ITICK) */ +}; + +struct clkent { + struct clkent *cke_next; /* MUST BE FIRST ELEMENT! See clk_qinsert */ + struct clkent *cke_prev; + int (*cke_rtn)(void *); /* Callout function, NULL if entry free */ + void *cke_arg; /* Argument to function */ + clkval_t cke_ticks; /* # relative ticks til timed out */ + + clkval_t cke_oticks; /* Original # ticks */ + int32 cke_usec; /* Original interval in usec */ + enum clksta cke_state; /* State of entry */ +}; + +typedef struct clkent *clktmr_t; /* External handle for clock timer */ + +enum { /* Return values for callout */ + CLKEVH_RET_KILL, /* de-register, can forget handle */ + CLKEVH_RET_QUIET, /* go quiescent, don't reschedule */ + CLKEVH_RET_REPEAT, /* repeat, using current interval */ + CLKEVH_RET_NOP /* Do nothing, handler has done something */ +}; + +struct clkregs { + struct clkent *clk_ctickq; /* ctick-res queue (doubly-linked) */ + struct clkent *clk_itickq; /* ITICK-res queue (doubly-linked) */ + struct clkent *clk_itickl; /* ITICK list (doubly-linked) */ + struct clkent *clk_quiet; /* Quiescent list (doubly-linked) */ + struct clkent *clk_free; /* Freelist (one-way) */ + + clkval_t clk_counter; /* Countdown: cticks of first entry */ + clkval_t clk_ocnt; /* Original countdown value */ + clkval_t clk_icnter; /* cticks thus far in interval */ + clkval_t clk_icntval; /* cticks per itick interval */ + int32 clk_ipms; /* # instrs per msec (CLKTRG only) */ + int32 clk_ipmsrq; /* Requested val of ipms (sigh) */ + int32 clk_tickusec; /* # usec per ctick */ + int32 clk_htickusec; /* (tickusec/2) for rounding */ + + int32 clk_itickusec; /* # usec per itick interval */ + int32 clk_hitickusec; /* (itickusec/2) for rounding */ + int32 clk_ithz; /* Interval timer rate in HZ (#/sec) */ + int32 clk_ithzfix; /* If set, fixes ITHZ value to use */ + int32 clk_itusfix; /* Same in usec */ + int32 clk_ithzosreq; /* Last OS-requested value in HZ */ + int32 clk_ithzcmreq; /* Cmd-requested val of ITHZ (sigh) */ +}; +/* Note: The "prev" of first entry on doubly-linked list points back +** to appropriate member above. Last entry's "next" is NULL. +*/ + + +/* Macros for use by client code that help shield it from knowing +** grubby details about the clock model. +** +** CLOCKPOLL() carries out a clock ctick update if synchronous. +** CLK_CTICKS_SINCE_ITICK() # of cticks so far in the current interval. +** CLK_CTICKS_UNTIL_ITICK() # of cticks left in current interval. +** CLK_CTICKS_PER_ITICK # of cticks in an interval. +** +** CLK_USEC_SINCE_ITICK() # of usec so far in the current interval. +** CLK_USEC_UNTIL_ITICK() # of usec left in current interval. +** CLK_USEC_PER_ITICK # of usec in an interval. +*/ +#if IFCLOCKED /* Synchronous counter model (also assuming ctick==usec) */ +# define CLOCKPOLL() if (--cpu.clk.clk_counter <= 0) CLK_TRIGGER() +# define CLK_CTICKS_SINCE_ITICK() (cpu.clk.clk_icnter + \ + (cpu.clk.clk_ocnt - cpu.clk.clk_counter)) +# define CLK_CTICKS_UNTIL_ITICK() (cpu.clk.clk_counter) +# define CLK_CTICKS_PER_ITICK (cpu.clk.clk_icntval) +# define CLK_USEC_SINCE_ITICK() CLK_CTICKS_SINCE_ITICK() +# define CLK_USEC_UNTIL_ITICK() CLK_CTICKS_UNTIL_ITICK() +# define CLK_USECS_PER_ITICK CLK_CTICKS_PER_ITICK +#else /* Real-time OSINT model */ +# define CLOCKPOLL() +# define CLK_CTICKS_SINCE_ITICK() (0) +# define CLK_CTICKS_UNTIL_ITICK() (0) +# define CLK_CTICKS_PER_ITICK (1) +# define CLK_USEC_SINCE_ITICK() (--ERROR--) +# define CLK_USEC_UNTIL_ITICK() (--ERROR--) +# define CLK_USECS_PER_ITICK (--ERROR--) +#endif + +/* Common constants */ + +#define CLK_USECS_PER_SEC ((int32)1000000) +#define CLK_USECS_PER_MSEC ((int32)1000) + +/* CLK_TRIGGER - Invoked when clock goes off, cause callouts now or later. +*/ +#if !KLH10_CLKXCT_IMMED +# define CLK_TRIGGER() (INTF_SET(cpu.intf_clk), INSBRKSET()) +#else +# define CLK_TRIGGER() clk_immtimeout() /* Execute immediate callouts */ +#endif + +/* Externally visible routines */ + +extern void clk_init(void); /* Initialize clock package */ +extern void clk_suspend(void), /* Suspend & resume clock */ + clk_resume(void); +extern void clk_idle(void); /* Idle CPU til next itick or I/O interrupt */ +extern void clk_synctimeout(void); /* At synch point, invoke update/callouts */ +extern void clk_immtimeout(void); /* At immed trigger, " " " */ + +/* CLK_ITICKSET - Set ITICK interval. +** "usec" is the new number of usec per itick. +*/ +extern void clk_itickset(int32 usec); /* Set interval tick in usec */ +extern void clk_ithzset(int hz); /* Same but in HZ */ + +/* Set virtual clock speed - instrs per msec. +** This defines the ratio of virtual usec to instruction cycles. +** Only meaningful when using KLH10_CLKTRG_COUNT. +** Default value of 1000 is a 1:1 ratio. +*/ +extern void clk_ipmsset(int32 ipms); + +/* Get a timer, with ITICK resolution. +** At *very* roughly "usec" from now, it will invoke a callout +** that executes "rtn(arg)". +** +*/ +extern struct clkent *clk_tmrget( + int (*rtn)(void *), void *arg, int32 usec); + +/* Get a timer, with CTICK resolution. +** Does not exist unless sub-iticks are supported. +*/ +#if KLH10_CLKRES_SUBITICK + extern struct clkent *clk_ctmrget( + int (*rtn)(void *), void *arg, int32 usec); +#endif + +/* Get a timer that fires every ITICK. +*/ +extern struct clkent *clk_itmrget(void (*rtn)(void *), void *arg); + +/* Change a timer's interval. +** Does nothing for every-ITICK timers obtained from clk_itmrget(). +*/ +extern void clk_tmrset(struct clkent *ce, int32 usec); + + +/* Make a timer quiescent. +** Interval and callout info is retained, but timer is removed +** from clock queues and will never be fired until re-activated. +** OK if already quiescent. +*/ +extern void clk_tmrquiet(struct clkent *ce); + +/* Make a timer active again. +** Put back on appropriate clock queue just like a new timer (ie +** its interval countdown is restarted from beginning). +** No-op if timer already active (doesn't change position on queues). +*/ +extern void clk_tmractiv(struct clkent *ce); + +/* Delete timer. +** Kills timer whether active or quiescent. +*/ +extern void clk_tmrkill(struct clkent *ce); + +#endif /* ifndef KN10CLK_INCLUDED */ diff --git a/src/kn10cpu.c b/src/kn10cpu.c new file mode 100644 index 0000000..361531a --- /dev/null +++ b/src/kn10cpu.c @@ -0,0 +1,3378 @@ +/* KN10CPU.C - Main Processor Operations (APR, PI, PAG) +*/ +/* $Id: kn10cpu.c,v 2.5 2001/11/19 10:43:28 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: kn10cpu.c,v $ + * Revision 2.5 2001/11/19 10:43:28 klh + * Add os_rtm_adjust_base for ITS on Mac + * + * Revision 2.4 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#include +#include /* For setjmp, longjmp */ + +#include "klh10.h" +#include "osdsup.h" +#include "kn10def.h" +#include "kn10ops.h" +#include "kn10dev.h" /* For device PI handling */ +#include "dvcty.h" /* For cty_ stuff */ + +#ifdef RCSID + RCSID(kn10cpu_c,"$Id: kn10cpu.c,v 2.5 2001/11/19 10:43:28 klh Exp $") +#endif + +/* Exported functions */ +void apr_init(void); +int apr_run(void); +void pi_devupd(void); +void apr_check(void); +void pxct_undo(void); /* Stuff needed by KN10PAG for page fail trap */ +void trap_undo(void); +#if KLH10_ITS_1PROC +void a1pr_undo(void); +#elif KLH10_CPU_KI || KLH10_CPU_KL +void afi_undo(void); +#endif + +/* Imported functions */ +extern void fe_begpcfdbg(FILE *); +extern void fe_endpcfdbg(FILE *); +extern void pishow(FILE *); +extern void pcfshow(FILE *, h10_t flags); +extern void pinstr(FILE *, w10_t w, int flags, vaddr_t e); +extern void fe_traceprint (register w10_t instr, vaddr_t e); + +/* Pre-declarations */ +static void trap_xct(void); +static int pi_check(void); +static void pi_xct(int lev); +static void pi_init(void); +static int tim_init(void); +#if KLH10_EXTADR + static int apr_creep(void); + static void apr_hop(void); +#else + static int apr_walk(void); + static void apr_fly(void); +#endif +#if KLH10_ITS_1PROC + static void apr_1proc(void); +#endif +#if KLH10_CPU_KI || KLH10_CPU_KL /* AFI Handling */ + static void apr_afi(void); +#endif +#if KLH10_CPU_KS + static void tim_rdtim(dw10_t *); + static void tim_wrtim(dw10_t *); +# if KLH10_RTIME_SYNCH + static void tim_basefreeze(void); + static void tim_baseunfreeze(void); +# endif +#endif /* KLH10_CPU_KS */ + +jmp_buf aprhaltbuf; /* Use this jump buffer to halt CPU, return to FE */ +jmp_buf aprloopbuf; /* Use this jump buffer to return to main APR loop */ + +/* APR_INIT - Called at startup to initialize APR "device" stuff +*/ +void +apr_init(void) +{ + INSBRK_INIT(); /* Ensure insbreak cleared */ + + cpu.aprf.aprf_set = cpu.aprf.aprf_ena = cpu.aprf.aprf_lev = 0; + cpu.mr_pcflags = 0; + PC_SET30(0); + PCCACHE_RESET(); +#if KLH10_CPU_KS +# if KLH10_SYS_ITS + LRHSET(cpu.mr_hsb, 0, 0500); /* Initial HSB base addr */ + LRHSET(cpu.mr_aprid, /* Set APR ID word */ + AIF_ITS | KLH10_APRID_UCVER, /* ITS ucode */ + KLH10_APRID_SERIALNO); +# else /* DEC */ + LRHSET(cpu.mr_hsb, 0, 0376000); /* Initial HSB base addr */ + LRHSET(cpu.mr_aprid, /* Set APR ID word */ + AIF_UBLT /* Have BLTUB, BLTBU */ + /* The INHCST and NOCST bits shouldn't be on for T20, but T20 + ** ignores them while T10 expects them with KL paging, so always + ** have them on. Sigh. + */ + | AIF_INHCST | AIF_NOCST /* For T10 */ + | (KLH10_PAG_KI ? AIF_KIPG : 0) + | (KLH10_PAG_KL ? AIF_KLPG : 0) + | KLH10_APRID_UCVER, + KLH10_APRID_SERIALNO); +# endif /* DEC */ + +#elif KLH10_CPU_KL + LRHSET(cpu.mr_aprid, + ( AIF_PMV + | (KLH10_PAG_KL ? AIF_T20 : 0) + | (KLH10_EXTADR ? AIF_EXA : 0) + | (KLH10_CPU_KLX ? AIF_KLB : 0) + | KLH10_APRID_UCVER), + ( AIF_CCA + | AIF_CHN + | (KLH10_MCA25 ? AIF_MCA : 0) + | (KLH10_CPU_KLX ? AIF_KLX : 0) + | KLH10_APRID_SERIALNO)); /* SN # */ +#endif + + cpu.mr_usrmode = cpu.mr_inpxct = cpu.mr_intrap = + cpu.mr_injrstf = cpu.mr_inpi = 0; +#if KLH10_ITS_1PROC + cpu.mr_in1proc = 0; +#elif KLH10_CPU_KI || KLH10_CPU_KL + cpu.mr_inafi = 0; +#endif + cpu.mr_dotrace = cpu.mr_1step = 0; + + op10m_setz(cpu.mr_dsw); /* Clear data switches initially */ + + pi_init(); /* Init the PI system */ + pag_init(); /* Init the pager */ + tim_init(); /* Init the timers/clocks */ +} + +/* APR_RUN - Invoked by FE to start PDP-10 running! +** All this routine does is select the actual main loop to use, +** based on the debugging or operating parameters in effect. +** +** Note that there are two different setjmp points. One is to allow +** halting the CPU loop; the other implements traps (PI interrupts etc) by +** re-entering the start of the CPU loop. The latter used to be inside +** the loop functions but has been moved outside because some compilers +** refuse to optimize anything that contains a setjmp! +*/ +int +apr_run(void) +{ + register int haltval; + + /* Save return point for APR halt */ + if (haltval = _setjmp(aprhaltbuf)) { /* If longjmp back, */ + clk_suspend(); /* stop internal clock */ + return haltval; /* return from running APR */ + } + clk_resume(); /* Resume internal clock */ + + /* Determine which APR loop to start or re-enter! */ + if (cpu.mr_bkpt || cpu.mr_dotrace || cpu.mr_1step +#if KLH10_SYS_T20 && KLH10_CPU_KS + || cpu.fe.fe_iowait || cpu.io_ctydelay +#endif + ) { + /* Debug loop of some sort - invoke slow loop */ + + /* Save point to return to for trap/interrupt */ + if (_setjmp(aprloopbuf)) { + if (cpu.mr_bkpt && PC_INSECT == cpu.mr_bkpt) + return HALT_BKPT; + } +#if KLH10_EXTADR + return apr_creep(); /* Extended (KL) */ +#else + return apr_walk(); /* Non-extended (KS) */ +#endif + } else { + /* No debugging - invoke normal fast loop, max speed */ + + _setjmp(aprloopbuf); /* Save return point for trap/interrupt */ +#if KLH10_EXTADR + apr_hop(); /* Extended (KL) */ +#else + apr_fly(); /* Non-extended (KS) */ +#endif + /* Fast loop shouldn't ever return, but if it does then we must + be doing some kind of manual debugging. + */ + return HALT_STEP; + } +} + +#if !KLH10_EXTADR +/* APR_WALK - Routine to use when debugging or some other kind of +** slow checking within the main loop may be needed. +** NON-EXTENDED operation. +*/ +static int +apr_walk(void) +{ + register w10_t instr; + + for (;;) { + register vaddr_t ea; + + /* Check for possible interrupt, trap, or special handling */ + if (INSBRKTEST()) { /* Check and handle all possible "asynchs" */ + apr_check(); + if (cpu.mr_bkpt && PC_INSECT == cpu.mr_bkpt) + return HALT_BKPT; + } + + /* Fetch next instruction from new PC (may page fault) */ + instr = vm_fetch(PC_VADDR); + + /* Compute effective address (may page fault) */ + ea = ea_calc(instr); /* Find it with inline macro */ + + /* Now dispatch after setting up AC (may page fault or set trap). + ** During execution, cpu.mr_PC contains PC of this instruction. It + ** is not incremented (if at all) until execution finishes. + */ +#if 1 + if (cpu.mr_dotrace) /* Show instruction about to be XCT'd */ + fe_traceprint(instr, ea); +#endif + + /* EXECUTE! */ + PC_ADDXCT(op_xct(iw_op(instr), iw_ac(instr), ea)); + +#if KLH10_SYS_T20 && KLH10_CPU_KS + /* Gross hack to emulate I/O device delays for buggy 10 software */ + if (cpu.io_ctydelay && (--cpu.io_ctydelay <= 0)) { + cty_timeout(); + } +#endif + CLOCKPOLL(); /* Update clock if necessary */ + +#if 1 /* Hack to support FE single-stepping */ + if (cpu.mr_1step && (--cpu.mr_1step <= 0)) { + cpu.mr_1step = 0; + return HALT_STEP; + } + if (cpu.mr_bkpt && PC_INSECT == cpu.mr_bkpt) + return HALT_BKPT; +#endif + } +} + + +/* APR_FLY - Primary loop when no debugging or special checks +** are necessary. +** NON-EXTENDED operation. +*/ +static void +apr_fly(void) +{ + register w10_t instr; +#if KLH10_PCCACHE + register vmptr_t vp; + register paddr_t pc; + register paddr_t cachelo = 1; /* Lower & upper bounds of PC */ + register paddr_t cachehi = 0; +#endif + + PCCACHE_RESET(); /* Robustness: invalidate cached PC info */ + for (;;) { + if (INSBRKTEST()) /* Check and handle all possible "asynchs" */ + apr_check(); +#if KLH10_PCCACHE + /* See if PC is still within cached page pointer */ + if ((pc = PC_INSECT) <= cachehi && cachelo <= pc + && cpu.mr_cachevp) { + vp = cpu.mr_cachevp + (pc & PAG_MASK); /* Win, fast fetch */ + } else { + vp = vm_xeamap(PC_VADDR, VMF_FETCH); /* Do mapping, may fault */ + /* Remember start of page or ac block */ + cpu.mr_cachevp = vp - (pc & PAG_MASK); + if (cachelo = (pc & (H10MASK & ~PAG_MASK))) { + /* Normal page reference */ + cachehi = cachelo | PAG_MASK; + } else if (pc > AC_17) { /* Page 0, special handling */ + cachelo = AC_17+1; + cachehi = PAG_MASK; + } else + cachehi = AC_17; /* Running in ACs */ + } + instr = vm_pget(vp); +#else + instr = vm_fetch(PC_VADDR); /* Fetch next instr */ +#endif + PC_ADDXCT(op_xct(iw_op(instr), iw_ac(instr), ea_calc(instr))); + +#if 0 /* Turn on to help debug this loop */ + if (cpu.mr_1step && (--cpu.mr_1step <= 0)) { + cpu.mr_1step = 0; + return HALT_STEP; + } +#endif + + CLOCKPOLL(); /* Update clock if necessary */ + } +} +#endif /* !KLH10_EXTADR */ + + +/* APR_INT - Abort current instruction; interrupt processor by returning +** to main loop. +** Someday might pass optional arg to longjmp for special dispatching +** into main loop. +*/ +void +apr_int(void) +{ +#if KLH10_ITS_1PROC + if (cpu.mr_in1proc) a1pr_undo(); /* Undo if in one-proceed */ +#elif KLH10_CPU_KI || KLH10_CPU_KL + if (cpu.mr_inafi) afi_undo(); /* Undo if in AFI */ +#endif + if (cpu.mr_intrap) trap_undo(); /* Undo if in trap instruction */ + if (cpu.mr_inpxct) pxct_undo(); /* Undo if PXCT operand */ + _longjmp(aprloopbuf, 1); +} + +/* APR_HALT - Halt processor. +** Passes reason back up to apr_run(). +*/ +void apr_halt(enum haltcode haltval) /* A HALT_xxx value */ +{ + _longjmp(aprhaltbuf, haltval); +} + +#if KLH10_EXTADR + +/* APR_CREEP - EXTENDED version of APR_WALK. +** Slow debug checking, slower with XA operation. +*/ +static int +apr_creep(void) +{ + register w10_t instr; + + for (;;) { + register vaddr_t ea; + + /* Check for possible interrupt, trap, or special handling */ + if (INSBRKTEST()) { /* Check and handle all possible "asynchs" */ + apr_check(); + if (cpu.mr_bkpt && PC_INSECT == cpu.mr_bkpt) + return HALT_BKPT; + } + + /* Fetch next instruction from new PC (may page fault) */ + instr = vm_PCfetch(); + + /* Compute effective address (may page fault) */ + ea = xea_calc(instr, PC_SECT); /* Find it with inline macro */ + + /* Now dispatch after setting up AC (may page fault or set trap). + ** During execution, cpu.mr_PC contains PC of this instruction. It + ** is not incremented (if at all) until execution finishes. + */ +#if 1 + if (cpu.mr_dotrace) /* Show instruction about to be XCT'd */ + fe_traceprint(instr, ea); +#endif + + /* EXECUTE! */ + PC_ADDXCT(op_xct(iw_op(instr), iw_ac(instr), ea)); + CLOCKPOLL(); /* Update clock if necessary */ + +#if 1 /* Hack to support FE single-stepping */ + if (cpu.mr_1step && (--cpu.mr_1step <= 0)) { + cpu.mr_1step = 0; + return HALT_STEP; + } + if (cpu.mr_bkpt && PC_INSECT == cpu.mr_bkpt) + return HALT_BKPT; +#endif + } +} + +/* APR_HOP - EXTENDED version of APR_FLY. +** Fast loop, hobbled by XA operations. +** No PC Cache stuff to begin with -- keep simple for now. +*/ +static void +apr_hop(void) +{ + register w10_t instr; + + for (;;) { + if (INSBRKTEST()) /* Check and handle all possible "asynchs" */ + apr_check(); + instr = vm_PCfetch(); /* Fetch next instr */ + + PC_ADDXCT(op_xct(iw_op(instr), iw_ac(instr), + xea_calc(instr, PC_SECT))); + +#if 0 /* Turn on to help debug this loop */ + if (cpu.mr_1step && (--cpu.mr_1step <= 0)) { + cpu.mr_1step = 0; + return HALT_STEP; + } +#endif + CLOCKPOLL(); /* Update clock if necessary */ + } +} +#endif /* EXTADR */ + + +/* APR_CHECK - Check all possible "interrupt" conditions to see what +** needs to be done, and do it. +** Clears mr_insbreak if nothing needed doing. +*/ +int apr_intcount = 0; + +void +apr_check(void) +{ +#if 0 + apr_intcount++; +#endif + for (;;) { /* Start of outer synchronous-handling loop */ + + INSBRK_ACTBEG(); /* Start inner asynch-handling loop */ + + /* Check for various interrupt-driven stuff */ + if (INTF_TEST(cpu.intf_fecty)) { + INTF_ACTBEG(cpu.intf_fecty); + INTF_ACTEND(cpu.intf_fecty); /* Immediately reset flag */ + apr_halt(HALT_FECTY); /* And halt processor! */ + } + +#if KLH10_EVHS_INT + if (INTF_TEST(cpu.intf_evsig)) { + INTF_ACTBEG(cpu.intf_evsig); + dev_evcheck(); + INTF_ACTEND(cpu.intf_evsig); + } +#endif +#if KLH10_CTYIO_INT + if (INTF_TEST(cpu.intf_ctyio)) { + INTF_ACTBEG(cpu.intf_ctyio); + cty_incheck(); + INTF_ACTEND(cpu.intf_ctyio); + } +#endif + if (INTF_TEST(cpu.intf_clk)) { + INTF_ACTBEG(cpu.intf_clk); + clk_synctimeout(); /* Invoke clock timeouts */ + INTF_ACTEND(cpu.intf_clk); + } + + /* Check for PI requests */ + { + register int pilev; + if (pilev = pi_check()) /* If PI interrupt requested, */ + pi_xct(pilev); /* handle it! */ + } + + /* Make sure no further asynchs have come in. This macro will + ** either loop or clear the insbreak flag. + */ + INSBRK_ACTEND(); + + /* OK, the insbreak flag is clear! Now OK to handle synchronous + ** trap conditions, where instructions must be executed. This cannot + ** be done within the inner asynch loop because iterative instructions + ** that do an INSBRKTEST would abort prematurely. + ** + ** This problem also potentially afflicts PI execution if the + ** interrupt instruction uses an indirect address, but the EA calc + ** code allows up to 1 indirection for speed before checking INSBRK. + ** If any monitor does double indirections then PI will hang + ** forever and this code must be re-thought. Just clearing INSBRK + ** is not good enough because some asynch event could turn INSBRK + ** back on during PI execution. Sigh. + */ +#if KLH10_ITS_1PROC + /* Any pending PI has been handled, now look for one-proceed trap */ + if (PCFTEST(PCF_1PR)) { + apr_1proc(); /* One-proceed next instruction */ + if (INSBRKTEST()) /* If it tickled the insbreak flag, */ + continue; /* restart outer loop! */ + } else +#elif KLH10_CPU_KI || KLH10_CPU_KL + /* Any pending PI has been handled, now see if hacking AFI */ + if (PCFTEST(PCF_AFI)) { + apr_afi(); /* Exert AFI on next instruction */ + if (INSBRKTEST()) /* If it tickled the insbreak flag, */ + continue; /* restart outer loop! */ + } else +#endif + /* Check for trap flags set. One-proceed/AFI also checks, so if + ** we did either, this is skipped. + */ + if (PCFTEST(PCF_TR1|PCF_TR2) && cpu.mr_paging) { + trap_xct(); /* Execute trap instruction */ + if (INSBRKTEST()) /* It may have tickled insbreak */ + continue; + } + + /* If we're here, nothing else to do, so resume normal execution */ + break; + } +} + +/* Effective Address Calculation. +** The routines here are full-blown functions called when the +** inline macros give up. +*/ + +w10_t +ea_wcalc(register w10_t iw, + register acptr_t acp, + register pment_t *map) +{ + register h10_t tmp; + + for (;;) { + if ((tmp = LHGET(iw)) & IW_X) { /* Indexing? */ + register w10_t wea; + wea = ac_xget(tmp & IW_X, acp); /* Get c(X) */ + LHSET(iw, LHGET(wea)); + RHSET(iw, (RHGET(iw)+RHGET(wea))&H10MASK); /* Add previous Y */ + } + if (!(tmp & IW_I)) /* Indirection? */ + return iw; /* Nope, return now! */ + iw = vm_pget(vm_xmap(RHGET(iw), /* Yup, do it */ + VMF_READ, + acp, map)); + if (iw_i(iw)) { /* If new word is indirect too */ + CLOCKPOLL(); + if (INSBRKTEST()) apr_int(); /* Stop infinite @ loops */ + } + } +} + +#if 1 + +vaddr_t +ea_fncalc(register w10_t iw, + register acptr_t acp, + register pment_t *map) +{ + register vaddr_t ea; + register h10_t tmp; + + tmp = LHGET(iw); + for (;;) { + if (tmp & IW_X) { /* Indexing? */ + /* Could optimize here by adding both words before masking; + ** another job for word10.h or kn10ops.h. + */ + va_lmake(ea, 0, + (RHGET(iw) + ac_xgetrh(tmp & IW_X, acp)) & H10MASK); + } else /* Not indexing, just use Y */ + va_lmake(ea, 0, RHGET(iw)); + + if (!(tmp & IW_I)) /* Indirection? */ + return ea; /* Nope, return now! */ + + /* Indirection, do it */ + iw = vm_pget(vm_xmap(ea, VMF_READ, acp, map)); + if ((tmp = LHGET(iw)) & IW_I) { /* If new word also indirect */ + CLOCKPOLL(); + if (INSBRKTEST()) apr_int(); /* Stop infinite @ loops */ + } + } +} +#endif + +#if KLH10_EXTADR + +/* XEA_XCALC - Extended Addressing EA calc!! +** This is it -- the monster that combines with vm_xmap to +** suck all the CPU cycles out of a KL emulation! +*/ +vaddr_t +xea_xcalc(register w10_t iw, /* IFIW to evaluate */ + register unsigned sect, /* Current section */ + register acptr_t acp, /* AC block mapping */ + register pment_t *map) /* Page table mapping */ +{ + register vaddr_t e; /* Address to return */ + + for (;;) { + if (op10m_tlnn(iw, IW_X)) { /* Indexing? */ + register w10_t xw; + xw = ac_xget(iw_x(iw), acp); /* Get c(X) */ + /* Check for type of indexing. + ** Do global only if NZS E, X>0, and X<6:17> NZ + */ + if (op10m_skipge(xw) && sect && op10m_tlnn(xw, VAF_SMSK)) { + /* Note special hackery for global indexing: Y becomes + ** a signed displacement. + */ + va_gmake30(e, VAF_30MSK & (va_30frword(xw) + + (op10m_trnn(iw, H10SIGN) + ? (RHGET(iw) | (VAF_SMSK<,,Y +** XCT 4,[MOVE 1,(X)] +** According to the doc, the EA is computed in current context, which +** results in a GLOBAL address of ,,Y that is then used for a ref +** in previous context (and bombs if section doesn't exist!). +** +** However, on a real KL, the reference is made to 0,,Y. Why? +** Don't know. Until someone figures out from prints +** exactly what is going on, the following hack is used to modify +** the algorithm to match the KL more closely: +** For case (2), if PCS=0, locality of E is ignored; mem ref +** is always made using PCS. +*/ +vaddr_t +xea_pxctcalc(register w10_t iw, + register unsigned sect, /* Current section */ + register acptr_t acp, /* AC block mapping */ + register pment_t *map, /* Page table mapping */ + int ebit, int dbit) /* E-calc and Data-ref bits from PXCT */ +{ + register vaddr_t e; + register int pxbits = cpu.mr_inpxct; + + if (ebit & pxbits) { /* If mapping E calc */ + /* E-bit set, do pre-proc but never post-proc */ + sect = pag_pcsget(); /* Make default section PCS */ + return xea_xcalc(iw, sect, acp, map); /* Compute E normally */ + } + + /* E-bit not set, may do post-proc */ + e = xea_xcalc(iw, sect, acp, map); /* Compute E normally */ + if ((pxbits & dbit) /* D set? (prev ctxt mem ref) */ + && (!(sect = pag_pcsget()) /* PCS==0? (SEE NOTE ABOVE!!) */ + || va_islocal(e))) { /* or E is local? */ + va_lmake(e, sect, va_insect(e)); /* Yup, apply PCS post-proc */ + } + return e; +} +#endif /* KLH10_EXTADR */ + +#if KLH10_CPU_KS + +/* UMOVE, UMOVEM - Move from and to user memory. +** Even though these are nominally identical to PXCT 4,[MOVE/M] +** they are implemented with their own routines, partly because +** there *is* one difference (legal in User-IOT mode) and partly +** because they are used in lots of places. +** Note the use of vm_xmap() which directly specifies the mapping to +** use, thus avoiding any need to set cpu.mr_inpxct. +** Unfortunately, these instructions are only defined for the KS, +** although they would be very handy for the KL as well. +*/ +insdef(i_umove) +{ + if (cpu.mr_usrmode && !PCFTEST(PCF_UIO)) /* Must be EXEC or User-IOT */ + return i_muuo(op, ac, e); + ac_set(ac, vm_pget( /* Do it, may page fault */ + vm_xmap(e, VMF_READ, cpu.acblk.prev, cpu.vmap.prev))); + return PCINC_1; +} + +insdef(i_umovem) +{ + if (cpu.mr_usrmode && !PCFTEST(PCF_UIO)) /* Must be EXEC or User-IOT */ + return i_muuo(op, ac, e); + vm_pset(vm_xmap(e, VMF_WRITE, cpu.acblk.prev, cpu.vmap.prev), + ac_get(ac)); /* Do it, may page fault */ + return PCINC_1; +} +#endif /* KS */ + +/* TRAP_XCT - Called when about to execute next instruction and notice +** that trap flags are set. Paging must be ON. +** PC points to not-yet-executed new instruction. In order to fake out +** instruction execution routines, we back this up by 1, which makes +** normal return work properly. +** However, any abnormal return to main loop (page fault or PI interrupt) +** must back out by undoing this! +** +** KLX notes: +** There is a serious problem here with extended addressing. +** In order to properly determine E for the trap instruction in an +** extended program, we must know the section # of the instruction that +** caused the trap (this constitutes the default section for EA computation). +** However, in the current KLH10 architecture, by the time we get here +** we can't know for sure where the guilty instruction was! PC has already +** been changed and if it was a jump of some kind then we can't tell +** what it was before (note normal increments and skips are always local, +** so for those we always know what PC section was). +** It may be possible to use the JPC feature to help out here. +** The only jumps that set Trap 1 are SOJ and AOJ. +** The only jumps that set Trap 2 are POPJ and PUSHJ. +** Finally, it's not clear which section is the default section if +** the trap occured as a result of an XCT chain. Hopefully it is *NOT* +** that of the final instruction word, since while we might be able to +** determine the correct section, there's no way we can find out from PC +** where the original XCT was. +** For the time being, we'll use PC section, and hope nothing loses +** too badly. +*/ +static void +trap_xct(void) +{ + register w10_t instr; + register paddr_t loc; + + /* Find location of trap instruction and fetch it. + ** This is a physical memory address, so no page fault is possible. + */ + switch (cpu.mr_pcflags & (PCF_TR1|PCF_TR2)) { /* Find trap offset */ + case 0: return; /* Not trapping?? */ + case PCF_TR1: loc = UPT_TR1; break; + case PCF_TR2: loc = UPT_TR2; break; + case (PCF_TR1|PCF_TR2): loc = UPT_TR3; break; + } + loc += (cpu.mr_usrmode ? cpu.mr_ubraddr : cpu.mr_ebraddr); /* Find loc in PT */ + instr = vm_pget(vm_physmap(loc)); /* Fetch the trap instr */ + +#if KLH10_DEBUG + if (cpu.mr_debug) { + fe_begpcfdbg(stderr); + fprintf(stderr,"TRAP:{"); + pinstr(stderr, instr, 0, 0L); /* 0L not right but ok for now */ + } +#endif + + cpu.mr_intrap = cpu.mr_pcflags & (PCF_TR1|PCF_TR2); /* Remember traps */ + cpu.mr_pcflags &= ~(PCF_TR1|PCF_TR2); /* Clear from flags */ + PC_ADD(-1); /* Fake 'em out */ + + /* Now execute it */ +#if KLH10_EXTADR + PC_ADDXCT(op_xct(iw_op(instr), iw_ac(instr), xea_calc(instr, PC_SECT))); +#else + PC_ADDXCT(op_xct(iw_op(instr), iw_ac(instr), ea_calc(instr))); +#endif + cpu.mr_intrap = 0; /* Normal return, no longer doing trap instr */ + +#if KLH10_DEBUG + if (cpu.mr_debug) { + putc('}', stderr); + fe_endpcfdbg(stderr); + } +#endif +} + +/* TRAP_UNDO - Called by abnormal instruction termination (abort or pagefault) +** while executing a trap instruction, to undo stuff properly. +*/ +void +trap_undo(void) +{ + if (cpu.mr_intrap) { + cpu.mr_pcflags &= ~(PCF_TR1|PCF_TR2); /* Clear from PC flags */ + cpu.mr_pcflags |= cpu.mr_intrap; /* Add old flags */ + cpu.mr_intrap = 0; + PC_ADD(1); /* Undo trap's backup of PC */ + INSBRKSET(); /* Want attention later */ + } +} + +#if KLH10_CPU_KS + +/* KS10 Clock and Interval Timing system +** +** See the PRM p.4-37 for a description of KS system timing. The original +** code here attempted to emulate the KS millisecond counter behavior by +** calling tim_ksupdate() at each millisecond countdown, but there is no +** real reason to do a timer update at anything less than the interval +** timer interrupt frequency, since all of the interaction between the +** monitor and the hardware is done via specific instructions that +** can take care to fake things out correctly. +** +** The KS10 basically has two ways of tracking time, a 71-bit +** time base and a 35-bit interval timer, both of which are +** run at exactly 4.1 MHz. The reason for this odd frequency appears to +** be an attempt to have the 12-bit internal counter count out at +** almost exactly one millisecond (1<<12 == 4096), but it really is +** exactly 4.1 MHz and not 4.096MHz!! Sigh, but close enough. +** +** This means each tick (low-order bit) represents 1/4.1 == 0.243902439... +** usec, and 4 ticks represent what ITS calls a "short usec" of +** 1/1.025 == .975 usec. Thus ignoring the low-order 2 bits gives +** a count of short usecs, and ignoring the low-order 12 bits gives a +** count of milliseconds (to be precise, 999.02439 usec). +** +** This code relies on the CLK facilities to provide either of two +** emulation methods: synchronous countdown, or OS run-time interrupts. +** See KN10CLK.C for more details. When using the synchronous method, +** each cycle tick should be considered equal to 1 virtual usec since +** that's roughly how fast a real KS10 executes instructions. +** +** The ITS pager's "quantum timer" is also driven off this counter so +** as to emulate a 1 MHz clock rate. On a KA the quantum timer returned by +** SPM is a 4.096/4 usec quantity. On a KL there is no quantum timer but +** it is approximated by performance counters to provide similar 4.096-usec +** units to ITS. On a KS (which implements SPM) the units are 3.9/16 usec +** (same as the time base) which ITS converts to 3.9-usec. +** Theoretically the quantum timer is continuously +** incremented whenever there is no PI in Progress. Since it is hacked +** in native mode, only 32 bits are supported, but that should be sufficient +** since the original KA-10 counter was only 18 bits. +** To emulate this with a low-resolution clock, the quantum counter is +** only updated whenever: +** PIP status changes: +** Enter PIP: freeze quant +** Leave PIP: unfreeze quant +** counter is read by ITS SPM instruction: freeze, read, unfreeze +** counter is set by ITS LMPR instruction: set, unfreeze +** millisecond clock ticks: add 1000 if unfrozen (not in PIP) +** The mechanics of un/freezing are: +** To unfreeze (start, so it can be ticked up by 1ms clock): +** quant = quant + +** To freeze value (stop, so it can be read): +** quant = quant - +** +** The basic idea is to normalize the quant counter whenever it is +** decoupled from the interval clock increment, by seeing how far it is in usec +** to the next interval tick. +** +** The same principle applies to maintaining the time base. +** +** Note that the code makes a simplifying assumption that each counter tick +** (ctick) is equivalent to 1/4 usec, rather than exactly 1/4.1 usec. +** This allows usec quantities to be added and subtracted from the counters +** simply by shifting them up by 2 bits. +*/ + +static void tim_ksupdate(void *); + +/* The main loop countdown is considered to be in units of short microseconds. +** Each such unit is equivalent to 4 timebase units. The countdown starts +** at a value calculated to reach zero after 1 millisecond, whereupon all +** other counters are updated. +*/ + +/* TIM_INIT - Initialize time stuff. KS version. +*/ +static int +tim_init(void) +{ + /* Init new internal clock */ + clk_init(); + +#if KLH10_ITIME_SYNCH + clk_itickset(CLK_USECS_PER_SEC/60); /* Default to 60HZ */ +#elif KLH10_ITIME_INTRP + clk_itickset(CLK_USECS_PER_SEC/30); /* Default to 30HZ */ +#endif /* (60 still too fast) */ + + clk_itmrget(tim_ksupdate, (char *)NULL); /* Put in 1st itick entry */ + +#if KLH10_RTIME_SYNCH + cpu.tim.tim_base[0] = 0; /* Initialize time base */ + cpu.tim.tim_base[1] = 0; + cpu.tim.tim_base[2] = 0; + tim_baseunfreeze(); /* Start it going */ + +#elif KLH10_RTIME_OSGET +# if KLH10_SYS_ITS /* ITS uses timebase as date/time clock if value OK */ + { + FILE *f; + + if ((f = fopen(TIMEBASEFILE, "r")) == NULL) { + LRHSET(cpu.tim.wrbase.w[0], 0, 0); + LRHSET(cpu.tim.wrbase.w[1], 0, 0); + os_rtmget(&cpu.tim.osbase); + return FALSE; + } + fread((char *)&cpu.tim.wrbase, sizeof(cpu.tim.wrbase), 1, f); + fread((char *)&cpu.tim.osbase, sizeof(cpu.tim.osbase), 1, f); + os_rtm_adjust_base(&cpu.tim.osbase, &cpu.tim.osbase, 0); + if (ferror(f)) { + fclose(f); + return FALSE; + } + fclose(f); + } +# else /* T20 (and T10?) use timebase as uptime clock, must clear */ + LRHSET(cpu.tim.wrbase.w[0], 0, 0); + LRHSET(cpu.tim.wrbase.w[1], 0, 0); + os_rtmget(&cpu.tim.osbase); +# endif /* !ITS */ +#endif + + return TRUE; +} + + +#if KLH10_QTIME_OSREAL || KLH10_QTIME_OSVIRT +static osrtm_t qcbase; + +/* Freeze - get realtime and compute # quantums used since last qcbase. */ +int32 +quant_freeze(int32 qc) +{ + osrtm_t rtm; + +#if KLH10_QTIME_OSREAL + os_rtmget(&rtm); /* Find current realtime */ +#else + os_vrtmget(&rtm); /* Find current virtual time used */ +#endif + os_rtmsub(&rtm, &qcbase); /* Get diff since last freeze */ + + /* Paranoia check - last line of defense against systems that + ** don't report runtime in a monotonically increasing way. + ** See os_vrtmget() for more detail. + */ +#if CENV_SYSF_BSDTIMEVAL + if (rtm.tv_sec < 0 || rtm.tv_usec < 0) { +# if 0 /* Disable output for now. Perhaps bump a meter later */ + fprintf(stderr, "[Neg quantum! %ld,%ld]\r\n", + (long)rtm.tv_sec, (long)rtm.tv_usec); +# endif + return qc; + } +#endif /* CENV_SYSF_BSDTIMEVAL */ + + return qc + (int32)os_rtm_toqct(&rtm); /* Convert to quantum ticks! */ +} + +/* Unfreeze - get realtime and remember as qcbase for current quantum count. */ +int32 +quant_unfreeze(int32 qc) +{ +#if KLH10_QTIME_OSREAL + os_rtmget(&qcbase); /* Find current realtime */ +#else + os_vrtmget(&qcbase); /* Find current virtual time used */ +#endif + return qc; +} +#endif /* OSREAL || OSVIRT */ + +#if KLH10_RTIME_SYNCH + +static void +tim_basefreeze(void) /* Freeze to read actual value */ +{ + register uint32 adjtim; + adjtim = CLK_USEC_UNTIL_ITICK() << 2; + cpu.tim.tim_base[0] = (cpu.tim.tim_base[0] - adjtim) & MASK32; + if (cpu.tim.tim_base[0] > -adjtim) { /* Check for underflow */ + if ((cpu.tim.tim_base[1] = (cpu.tim.tim_base[1]-1)&MASK32) == MASK32) + cpu.tim.tim_base[2]--; + } +} + +static void +tim_baseunfreeze(void) +{ + register uint32 adjtim; + adjtim = (CLK_USEC_UNTIL_ITICK() << 2); + cpu.tim.tim_base[0] = (cpu.tim.tim_base[0] + adjtim) & MASK32; + if (cpu.tim.tim_base[0] < adjtim) { /* Check for overflow */ + if ((cpu.tim.tim_base[1] = (cpu.tim.tim_base[1]+1)&MASK32)==0) + cpu.tim.tim_base[2]++; + } +} +#endif /* KLH10_RTIME_SYNCH */ + +/* TIM_KSUPDATE - Invoked when KS10 interval timer goes off; +** either OS timer fired, or internal synch counter counted out. +*/ +static void +tim_ksupdate(void *ignored) +{ +#if KLH10_RTIME_SYNCH + /* Update time base. */ + { + int32 intv4 = CLK_USECS_PER_ITICK << 2; /* Interval in 1/4 us units */ + + if (((cpu.tim.tim_base[0] += intv4) & MASK32) < intv4) + if ((cpu.tim.tim_base[1] = (cpu.tim.tim_base[1]+1)&MASK32) == 0) + ++cpu.tim.tim_base[2]; + } +#endif + +#if KLH10_SYS_ITS && KLH10_QTIME_SYNCH + /* Update quantum timer if not PIP. */ + if (!cpu.pi.pilev_pip) + cpu.pag.pr_quant += (CLK_USECS_PER_ITICK<<2); +#endif + + /* Attempt PI if interval done. + ** For now assume that call to tim_ksupdate() always implies interval done, + ** so always fire if permitted. + */ + if (op10m_skipn(cpu.tim.tim_intreg)) { /* If interval cntr is set */ + cpu.aprf.aprf_set |= APRF_TIM; /* "Timer Interval Done" */ + apr_picheck(); /* Check to trigger PI */ + } +} + +#if KLH10_DEBUG +int tim_debug = 0; /* Hack for KS timebase debugging */ +#endif + +/* TIM_RDTIM - Get time base using OS real time. +*/ +static void +tim_rdtim(register dw10_t *tbase) +{ +#if KLH10_RTIME_SYNCH + register w10_t hi, lo; + + /* Freeze/normalize the time-base quantity at this point */ + tim_basefreeze(); + +#define TB cpu.tim.tim_base + RHSET(lo, TB[0] & (H10MASK&~03)); /* Low 18 bits in RH */ + LHSET(lo, (TB[0] >> 18) & (((h10_t)1<<(32-18))-1)); /* 14 bits in LH */ + LHSET(lo, (LHGET(lo) | (TB[1] << 14)) & MASK17); /* 3 more */ + /* Note sign bit of low word must be clear, ditto bottom 2 bits */ + RHSET(hi, (TB[1] >> 3) & H10MASK); /* Shove 18 into RH */ + LHSET(hi, (TB[1] >> 21) & (((h10_t)1<<(32-21))-1)); /* 11 more bits */ + LHSET(hi, (LHGET(hi) | (TB[2] << 11)) & H10MASK); /* All rest */ +#undef TB + tim_baseunfreeze(); + tbase->w[0] = hi; + tbase->w[1] = lo; + +#elif KLH10_RTIME_OSGET + osrtm_t rtm; + + if (!os_rtmget(&rtm)) { + op10m_setz(tbase->w[0]); + op10m_setz(tbase->w[1]); + return; + } + + /* Now find difference between current time and old systime */ + os_rtmsub(&rtm, &cpu.tim.osbase); +#if KLH10_DEBUG + if (tim_debug) + printf("OS time diff: %ld sec, %ld usec\r\n", + (long)OS_RTM_SEC(rtm), (long)OS_RTM_USEC(rtm)); +#endif + /* Now convert this quantity to KS10 ticks, add that to old timebase, + ** and return the new timebase value. + ** A real pain on these crippled 32-bit machines. + */ + os_rtm_tokst(&rtm, tbase); + +#if KLH10_DEBUG + if (tim_debug) + printf("KS time diff: %#lo,,%#lo %#lo,,%#lo\r\n", + (long) LHGET(HIGET(*tbase)), (long) RHGET(HIGET(*tbase)), + (long) LHGET(LOGET(*tbase)), (long) RHGET(LOGET(*tbase)) ); + if (tim_debug) + printf("KS old time: %#lo,,%#lo %#lo,,%#lo\r\n", + (long) LHGET(HIGET(cpu.tim.wrbase)), (long) RHGET(HIGET(cpu.tim.wrbase)), + (long) LHGET(LOGET(cpu.tim.wrbase)), (long) RHGET(LOGET(cpu.tim.wrbase)) ); +#endif + + /* Slightly faster double addition. Assumes low signs always 0 */ + op10m_udaddw(*tbase, cpu.tim.wrbase.w[1]); /* First add low word */ + op10m_add(tbase->w[0], cpu.tim.wrbase.w[0]); /* Then add high word */ + +#if KLH10_DEBUG + if (tim_debug) + printf("KS new time: %#lo,,%#lo %#lo,,%#lo\r\n", + (long) LHGET(HIGET(*tbase)), (long) RHGET(HIGET(*tbase)), + (long) LHGET(LOGET(*tbase)), (long) RHGET(LOGET(*tbase)) ); +#endif + +#endif +} + +/* TIM_WRTIM - Set time base, relative to OS real time. +** Makes permanent record of offset so that the next KLH10 +** startup can pretend that the clock kept running. +*/ +static void +tim_wrtim(register dw10_t *tbase) +{ +#if KLH10_RTIME_SYNCH + dw10_t d; + + d = *tbase; + tim_basefreeze(); /* Stop, to get valid current time */ +#define TB cpu.tim.tim_base + TB[0] &= ~MASK12; /* Keep bottom 12 bits of current time! */ + TB[0] |= ((uint32)LHGET(LOGET(d)) << 18) | (RHGET(LOGET(d))&~MASK12); + TB[1] = (LHGET(LOGET(d)) >> (18-(35-32))) & 07; /* High 3 */ + TB[1] |= (((uint32)LHGET(HIGET(d)) << 18) | RHGET(HIGET(d))) << 3; + TB[2] = LHGET(HIGET(d)) >> (32-(18+3)); +#undef TB + tim_baseunfreeze(); + +#elif KLH10_RTIME_OSGET + FILE *f; + osrtm_t absolute_osbase; + + cpu.tim.wrbase = *tbase; + op10m_andcmi(cpu.tim.wrbase.w[1], MASK12); /* Zap low 12 bits */ + + if (!os_rtmget(&cpu.tim.osbase)) { + return; + } +# if KLH10_SYS_ITS /* ITS wants timebase to endure over reloads */ + if ((f = fopen(TIMEBASEFILE, "w")) == NULL) + return; + fwrite((char *)&cpu.tim.wrbase, sizeof(cpu.tim.wrbase), 1, f); +# if 1 + os_rtm_adjust_base(&cpu.tim.osbase, &absolute_osbase, 1); + fwrite((char *)&absolute_osbase, sizeof(absolute_osbase), 1, f); +# else + fwrite((char *)&cpu.tim.osbase, sizeof(cpu.tim.osbase), 1, f); +# endif + fclose(f); +# endif /* ITS */ +#endif +} + + +/* IO_RDTIM - (CONO TIM,) +*/ +ioinsdef(io_rdtim) +{ + register vmptr_t p0, p1; + dw10_t d; + + p0 = vm_xrwmap(e, VMF_WRITE); /* Check access for 1st word */ + va_inc(e); /* Bump E by 1, may wrap funnily */ + p1 = vm_xrwmap(e, VMF_WRITE); /* Check access for 2nd word */ + + tim_rdtim(&d); /* Read time double-word into here */ + vm_pset(p0, HIGET(d)); /* OK, set the double-word */ + vm_pset(p1, LOGET(d)); + return PCINC_1; +} + +/* IO_WRTIM - (CONO MTR,) Set Time Base +** Note that the bottom 12 bits cannot be set. They are left as whatever +** they already were in the time base registers. +*/ +ioinsdef(io_wrtim) +{ + dw10_t d; + vm_dread(e, d); /* Fetch the double */ + tim_wrtim(&d); /* Store it as time base! */ + return PCINC_1; +} + +/* IO_RDINT - (CONI TIM,) +*/ +ioinsdef(io_rdint) +{ + vm_write(e, cpu.tim.tim_intreg); /* Store interval register into E */ + return PCINC_1; +} + +/* IO_WRINT - (CONI MTR,) Set KS10 interval counter period from c(E) +** +** The interval counter counts down in the same units as the time +** base (at 4.1MHz, each tick represents 1/4.1 usec) +** This instruction is only invoked once during monitor startup +** so doesn't need to be fast, and only a few specific cases are provided +** for. +** ITS uses a 60 Hz period (0205355) +** T20 uses a 1000 Hz period ( 010004) +** T10 uses a 60 Hz period (0200000) but twiddles it incessantly +** for leap jiffy, leap msec, etc. +** +** It isn't clear what happens if the period is set to 0. This code +** assumes it turns the counter off. It's possible a real KS might +** interpret this as interrupting the next time the counter overflows, +** which could be anywhere from 1 usec to 140 minutes. +*/ +ioinsdef(io_wrint) +{ + register w10_t w; + + w = vm_read(e); /* Read new interval register from E */ + cpu.tim.tim_intreg = w; + +#if KLH10_ITIME_INTRP + INTF_INIT(cpu.intf_clk); + if (op10m_skipn(w)) { + register uint32 usec; + + if (!LHGET(w)) { + if (RHGET(w) == 0205355) /* ITS: 1/60 sec? */ + usec = CLK_USECS_PER_SEC/60; + else if (RHGET(w) == 01004) /* T20: 1ms? */ + usec = CLK_USECS_PER_MSEC; + else if ((0200000 <= RHGET(w)) /* T10: within "60Hz" range? */ + && (RHGET(w) <= 0220000)) + usec = CLK_USECS_PER_SEC/60; + else + goto gencase; + } + else if (LHGET(w) < 037777) { /* Ensure no ovfl */ + gencase: + /* General case: usec = (ticks * 1/4.1) + ** Compute this as ((ticks * 10) / 41) + */ + usec = (LHGET(w) << H10BITS) | RHGET(w); + usec = (usec * 10) / 41; + } else { + fprintf(stderr, "[io_wrint: Unsupported interval: %lo,,%lo]\r\n", + (long)LHGET(w), (long)RHGET(w)); + return PCINC_1; /* Do nothing */ + } + + /* Ask to change interval timer. Normally this request will + ** be ignored, unless KLH10 user has cleared clk_ithzfix. + ** See corresponding KL code in io_co_tim(). + */ + clk_itickset(usec); /* Set timer going! */ + + } else { +#if 0 + /* Don't allow KS to get any more interval timer ints, but + ** must keep internal CLK timer alive. + */ +#endif + } +#endif /* KLH10_ITIME_INTRP */ + + return PCINC_1; +} +#endif /* KLH10_CPU_KS */ + +#if KLH10_CPU_KL + +/* KL timer and meter code */ + +/* +The KL10 has two different time counters, a time base and an interval counter, +very similar to those for the KS. + +INTERVAL COUNTER: + The interval counter is a 12-bit 100kHz counter (each unit is +exactly 10 usec). The minimum interval is thus 10 usec to 40.950 +msec. TOPS-20 sets its interval to 0144 (100.) so that it interrupts +every 1ms. + Whenever the countup reaches a value equal to the interval +period, it sets its "Done" flag (which may trigger a PI), clears +itself, and starts over. If the count reaches its maximum value +without matching the set period (this can only happen if the period is +set at a time when the count is already greater than the period), then +both the "Done" and "Overflow" flags are set. + PI for the interval counter is specially handled -- it +executes the instruction at EPT+514. Hence it needs to be checked for +in a slightly different way. + + Synchronous mode emulation: each instruction is interpreted as 1 usec + and the interval period used for the clock poll countdown. + If the interval counter is off, a default period of 1ms + is used for clock polling. + Interrupt mode emulation: the interval period is translated into + suitable host platform units. Currently there may be a + check for T20's 1ms period to silently substitute a more + manageable 10ms period instead. + +TIME BASE: + The time base is one of the peculiar KL double-word counts. +The 16-bit hardware count portion is incremented at 1MHz (one tick = 1 +usec); note this is shifted up 12 bits from the low end of the doubleword. +(TOPS-20 basically ignores those low 12 bits.) + + Synchronous mode emulation: As for the interval counter, each + instruction is interpreted as 1 usec and the time base + maintained from the clock poll countdown (which has a + period corresponding to either the interval counter or + the default polling count). + OS mode emulation: every RDTIME request instigates a host platform + system call for the current time-of-day in units as small + as feasible. From this is subtracted the OS time base + as of the last KL timebase check, and the result + translated into 1MHz units. + +Beware: Only 32 bits of hardware counter are guaranteed, which at 1MHz is + approximately 4K sec == 1 hour. There will be a time loss for + KLH10_RTIME_OSGET if execution is suspended for that long! +*/ + +static void tim_klupdate(void *); + +/* TIM_INIT - Initialize time stuff. KL version. +*/ +static int +tim_init(void) +{ + /* Initialize interval counter */ + cpu.tim.tim_on = 0; + cpu.tim.tim_flgs = 0; + cpu.tim.tim_intper = 0; + cpu.tim.tim_intcnt = 0; + + /* Initialize time base */ +#if KLH10_RTIME_SYNCH + cpu.tim.tim_tbase = 0; +#elif KLH10_RTIME_OSGET + if (!os_rtmget(&cpu.tim.tim_osbase)) { /* Get OS realtime */ + fprintf(stderr, "tim_init: Cannot get OS realtime!\n"); + return FALSE; + } +#endif + + /* Init internal clock */ + clk_init(); + clk_itickset(CLK_USECS_PER_SEC/30); /* Default to 30HZ */ + /* (60 still too fast) */ + clk_itmrget(tim_klupdate, (char *)NULL); /* Put in 1st itick entry */ + + return TRUE; +} + +/* TIM_KLUPDATE - Invoked once per ITICK (interval timer tick) +** KL version. +*/ +static void +tim_klupdate(void *ignored) /* Arg is unused */ +{ +#if KLH10_RTIME_SYNCH + /* Update time base by adding # of usec since last itick update */ + cpu.tim.tim_tbase += CLK_USECS_PER_ITICK; +#endif + + /* Attempt PI if interval done. + ** For now assume that call to tim_klupdate() always implies interval done. + */ + if (cpu.tim.tim_on) { + cpu.tim.tim_intcnt = 0; /* Reset countup */ + + cpu.tim.tim_flgs |= TIM_RDONE; /* "Timer Interval Done" */ + if (cpu.pi.pilev_timreq |= cpu.tim.tim_lev) /* Add PI if any */ + pi_devupd(); /* Check to trigger intrupt */ + } +} + +/* General-purpose utility to update counter doublewords +** Note hair to ensure that "hardware" counter is shifted up by 12. +*/ +#define DWCNT_UPDATE(pa, cntvar) \ + { vp = vm_physmap(pa); \ + d = vm_pgetd(vp); /* Get double (EPT/UPT is known safe ref) */\ + if ((cntvar) & ~MASK23) /* Add high bits if any */\ + op10m_addi(d.w[0], ((cntvar)>>23) & H10MASK); \ + LRHSET(w, ((cntvar) >> (H10BITS-12))&H10MASK, ((cntvar)<<12) & H10MASK); \ + op10m_udaddw(d, w); /* Add W into D */\ + vm_psetd(vp, d); /* Store back into original loc as update */\ + (cntvar) = 0; /* Clear "hardware" counter */\ + } + +static pcinc_t +mtr_store(register unsigned int pa, + register uint32 *acnt, + register vaddr_t e) +{ + register w10_t w; /* Vars needed for MTR_DWUPDATE */ + register dw10_t d; + register vmptr_t vp; + + if (cpu.mr_paging) { + DWCNT_UPDATE(pa, *acnt) /* Do update, leave dword in d */ + } else { + /* If no paging, can't refer to EPT or UPT. Just pass on + ** value of hardware counter, and don't reset it. + */ + LRHSET(d.w[0], 0, (*acnt >> 23) & H10MASK); /* Hi bits into hi wd */ + LRHSET(d.w[1], (*acnt >> (H10BITS-12))&H10MASK, + (*acnt << 12) & H10MASK); + } + + /* Now store in desired location */ + vm_dwrite(e, d); /* Can pagefail after all this, sigh */ + return PCINC_1; +} + + +/* RDPERF (70200 = BLKI TIM,) Read Performance Analysis Count +** Read process execution time doubleword from EPT+512, +** add current hardware counter, store result in E, E+1. +** NOTE: PRM 3-61 is *INCORRECT*. Description there is a duplicate of +** that for RDEACT. EPT map on p.3-30 is right. +*/ +ioinsdef(io_rdperf) +{ + return mtr_store((cpu.mr_ebraddr + EPT_PRF), /* EPT+512 */ + &cpu.tim.tim_perf, + e); +} + +/* RDTIME (70204 = DATAI TIM,) Read Time Base +** Read time base doubleword from EPT+510, +** add current hardware time base counter, store result in E, E+1. +*/ +/* + The KL10 time base is a 59-bit doubleword integer (low sign is +0, low 12 bits are ignored) that increments at exactly 1MHz (1 each usec). +It's OK to set the last 12 bits if the extra precision is available from +the host platform. + +Note that 20 bits are needed to store a 1M value. Thus 32 bits is +exactly enough to hold the result of left-shifting a value up to 1M +by 12 bits (for adding into low part of word). +*/ +/* NOTE: + It appears that the only place the T20 monitor actually reads the +EPT timebase contents (rather than using RDTIME) is a place in APRSRV +called GETMID where the low word is used as a unique structure media ID. +(extremely bogusly, if you ask me). + It would be VERY nice if the EPT update could be totally flushed +and the timebase merely returned in response to a RDTIME. But if GETMID +is to be supported (and it looks suspiciously important), then EPT+510 +needs to be updated every so often just on the .000001% probability it +might be needed. + Perhaps a closer examination of the caller of GETMID (WRTHOM in +DSKALC.MAC) might reveal something else we could trigger the update on. +The best candidate seems to be a WRTIME (CONO MTR,) that resets the +time base; by setting the low 12 bits (otherwise unused) of the location +to some random fixed value, can ensure that the initial structure always gets +that value as its media ID. + +*/ +ioinsdef(io_rdtime) +{ +#if KLH10_RTIME_SYNCH + /* Update internal time base value. + ** Note post-update hair. This is needed because CLK_USEC_SINCE_ITICK is + ** the # of usecs since last interval tick, and is not reset, so if + ** this was called repeatedly within one itick the timebase would + ** incorrectly have a lot of extra cticks added. Sigh. + */ + int32 cticks = CLK_USEC_SINCE_ITICK(); + + cpu.tim.tim_tbase += cticks; + (void) mtr_store((cpu.mr_ebraddr + EPT_TBS), /* EPT+510 */ + &cpu.tim.tim_tbase, + e); + cpu.tim.tim_tbase = -cticks; /* Reset with negative val! */ + return PCINC_1; + +#elif KLH10_RTIME_OSGET + uint32 ibase; + osrtm_t rtm, rtmbase; + + if (!os_rtmget(&rtm)) /* Get OS realtime */ + panic("io_rdtime: Cannot get OS realtime!"); + rtmbase = rtm; /* Remember for new base */ + os_rtmsub(&rtm, &cpu.tim.tim_osbase); /* Find elapsed realtime */ + cpu.tim.tim_osbase = rtmbase; /* Set base for next time */ + ibase = os_rtm_toklt(&rtm); /* Convert diff to KL ticks */ + return mtr_store((cpu.mr_ebraddr + EPT_TBS), /* EPT+510 */ + &ibase, + e); +#endif +} + +/* WRPAE (70210 = BLKO TIM,) Write Performance Analysis Enables +** Select counting method and conditions for PERF counter, based +** on c(E). +** Makes a read reference but otherwise is a nop for now. +*/ +ioinsdef(io_wrpae) +{ + (void) vm_redmap(e); /* Do a read reference */ + return PCINC_1; +} + +/* CONO TIM, (70220) Conditions Out, Interval Counter +** Set up interval counter according to E. +** It's unclear what a zero interval might mean. For the time +** being I'm interpreting it as 1<<12 (4096.) +*/ +ioinsdef(io_co_tim) +{ + register h10_t ebits = va_insect(e); + register unsigned int newper = ebits & TIM_PERIOD; /* 12 bits */ + + if (!newper) /* Interpret a zero period as 4096 */ + newper = 1<<12; + + /* Check for clearing TIM_RDONE and TIM_ROVFL flags */ + if (ebits & TIM_WFCLR) { + cpu.tim.tim_flgs = 0; /* Clear Done & Overflow flags */ + if (cpu.pi.pilev_timreq) { /* If PI was set, */ + cpu.pi.pilev_timreq = 0; /* clear it, and */ + pi_devupd(); /* propagate the change */ + } + } + + /* Not clear (:-) how to clear counter in middle of a countdown. + ** Assume it only happens when turning things off (in which case + ** value is irrelevant) or on (in which case we simply restart). + */ + if (ebits & TIM_WCLR) + cpu.tim.tim_intcnt = 0; /* Clear countup */ + + /* See if changing either the period or the on/off state. + ** Generally they are changed only once at monitor startup and never + ** thereafter, although CONO TIM, must be done after every interrupt + ** in order to turn off the DONE bit. + */ + if (newper != cpu.tim.tim_intper + || (cpu.tim.tim_on != (ebits & TIM_ON))) { + if (ebits & TIM_ON) { /* Turn interval counter on? */ + /* Changing period can glitch all meters (and timebase) unless + ** they are partially incremented to compensate. But ignore that + ** for the time being since no known monitor ever changes in + ** midstream. + ** Note previous code that prevents newper from being 0. + */ +#if KLH10_ITIME_SYNCH + /* For now, ignore "minor" change attempts. T10 tries to + ** twiddle this incessantly for a "leap jiffy" and it will + ** never do any good. So avoid wasting the overhead... + ** This check ignores attempts to reduce the period or increase it + ** by less than 3. + */ + if (((int)(newper - cpu.tim.tim_intper)) > 2) + clk_itickset(((uint32)newper)*10); + +#elif KLH10_ITIME_INTRP + /* Go ahead and ask the clock code for a new timer period. + ** Normally this request will be ignored in favor of a fixed + ** runtime parameter (clk_ithzfix), unless the user has cleared it. + ** This both cures T10's twiddling (centered around 60Hz) + ** and avoids T20's too-fast 1ms tick. + */ + clk_itickset(((uint32)newper)*10); +#endif + } else { /* Turning counter off */ + /* No explicit turnoff of CLK internal timer, cuz other things + ** like peripheral device timeouts may depend on it. + ** Clearing TIM_ON bit will suffice to ensure that 10 doesn't + ** receive unexpected interval interrupts. + ** + ** Doesn't keep interval counter state from changing; oh well. + */ + } + cpu.tim.tim_on = (ebits & TIM_ON); /* Set flag to new state */ + cpu.tim.tim_intper = newper; + } + + return PCINC_1; +} + +/* CONI TIM, (70224) Conditions In, Interval Counter +** Read status of interval counter into c(E). +*/ +ioinsdef(io_ci_tim) +{ + register w10_t w; + + if (cpu.tim.tim_on) { +#if KLH10_ITIME_SYNCH /* Get 10usec units */ + cpu.tim.tim_intcnt = CLK_USEC_SINCE_ITICK() / 10; +#elif KLH10_ITIME_INTRP + cpu.tim.tim_intcnt++; /* Can't tell, so just bump each time */ +#endif + } + LRHSET(w, (cpu.tim.tim_intcnt & MASK12), + cpu.tim.tim_on | cpu.tim.tim_flgs | cpu.tim.tim_intper); + + vm_write(e, w); + return PCINC_1; +} + +/* CONSZ TIM, (70230) Skip if all TIM status bits in non-zero E are zero. +*/ +ioinsdef(io_sz_tim) +{ +#if 1 + return (va_insect(e) & + (cpu.tim.tim_on | cpu.tim.tim_flgs | cpu.tim.tim_intper)) + ? PCINC_1 : PCINC_2; +#else + return (va_insect(e) + && !(va_insect(e) & + (cpu.tim.tim_on | cpu.tim.tim_flgs | cpu.tim.tim_intper))) + ? PCINC_2 : PCINC_1; +#endif +} + +/* CONSO TIM, (70234) Skip if any TIM status bits in E are set. +*/ +ioinsdef(io_so_tim) +{ + return (va_insect(e) & + (cpu.tim.tim_on | cpu.tim.tim_flgs | cpu.tim.tim_intper)) + ? PCINC_2 : PCINC_1; +} +#endif /* KL */ + +#if KLH10_CPU_KL + +/* Device MTR */ + + +/* MTR_UPDATE - Carry out the "update accounts" operation +** described by PRM 3-57 for the Execution and Memory accounts. +** Called by pager code when UBR is set. +*/ +void +mtr_update(void) +{ + register w10_t w; /* Vars needed for DWCNT_UPDATE */ + register dw10_t d; + register vmptr_t vp; + + if (cpu.tim.mtr_on && cpu.mr_paging) { + DWCNT_UPDATE(cpu.mr_ubraddr + UPT_MRC, cpu.tim.mtr_mact) + DWCNT_UPDATE(cpu.mr_ubraddr + UPT_PXT, cpu.tim.mtr_eact) + } +} + + +/* RDMACT (70240 = BLKI MTR,) Read Memory Account +** Read memory reference doubleword from UPT+506, +** add current hardware counter, store result in E, E+1. +*/ +ioinsdef(io_rdmact) +{ + return mtr_store( + (cpu.mr_ubraddr + UPT_MRC), /* UPT+506 */ + &cpu.tim.mtr_mact, + e); +} + +/* RDEACT (70244 = DATAI MTR,) Read Execution Account +** Read process execution time doubleword from UPT+504, +** add current hardware counter, store result in E, E+1. +*/ +ioinsdef(io_rdeact) +{ + return mtr_store( + (cpu.mr_ubraddr + UPT_PXT), /* UPT+504 */ + &cpu.tim.mtr_eact, + e); +} + +/* WRTIME (70260 = CONO MTR,) Conditions Out, Meters +** Set up meters and timing. +** See comments for io_rdtime() regarding time base setup. +*/ +ioinsdef(io_wrtime) +{ + register uint18 erh = va_insect(e); + + if (erh & MTR_SUA) { + /* Set up Accounts */ + + /* Reflect new flag bits in test words */ + cpu.tim.mtr_on = (erh & MTR_AON); + cpu.tim.mtr_ifexe = (erh & MTR_AEPI); + cpu.tim.mtr_ifexepi = (erh & MTR_AENPI); + + /* Actually do something about it? */ + } + + /* Remember flags for CONI */ + cpu.tim.mtr_flgs = erh & (MTR_AEPI|MTR_AENPI|MTR_AON|MTR_TBON|MTR_ICPIA); + + /* Set PIA for interval counter */ + cpu.tim.tim_lev = pilev_bits[erh & MTR_ICPIA]; + if (cpu.pi.pilev_timreq) { /* If PI already being requested */ + cpu.pi.pilev_timreq = cpu.tim.tim_lev; /* may need to change lev */ + pi_devupd(); /* and re-check priority */ + } + + /* Take care of time base */ + if (erh & MTR_TBOFF) + cpu.tim.tim_tbon = 0; + if (erh & MTR_TBON) + cpu.tim.tim_tbon = MTR_TBON; + if (erh & MTR_TBCLR) { /* Reset time base hardware ctr */ +#if KLH10_RTIME_SYNCH + cpu.tim.tim_tbase = -CLK_USEC_SINCE_ITICK(); /* Note neg offset! */ +#elif KLH10_RTIME_OSGET + (void) os_rtmget(&cpu.tim.tim_osbase); +#endif + /* Special hack. See comments at io_rdtime for explanation. */ + if (cpu.mr_paging) { + vmptr_t vp = vm_physmap(cpu.mr_ebraddr + EPT_TBS + 1); + vm_psetrh(vp, vm_pgetrh(vp) | 01367); /* 759. */ + } + } + + return PCINC_1; +} + +/* CONI MTR, (70264) Conditions In, Meters +** Read status of meters & timers into c(E). +*/ +ioinsdef(io_ci_mtr) +{ + register w10_t w; + + LRHSET(w, 0, cpu.tim.mtr_flgs); + vm_write(e, w); + return PCINC_1; +} + +/* CONSZ MTR, (70270) Skip if all MTR status bits in non-zero E are zero. +*/ +ioinsdef(io_sz_mtr) +{ +#if 1 + return (va_insect(e) & cpu.tim.mtr_flgs) + ? PCINC_1 : PCINC_2; +#else + return (va_insect(e) && !(va_insect(e) & cpu.tim.mtr_flgs)) + ? PCINC_2 : PCINC_1; +#endif +} + +/* CONSO MTR, (70274) Skip if any MTR status bits in E are set. +*/ +ioinsdef(io_so_mtr) +{ + return (va_insect(e) & cpu.tim.mtr_flgs) + ? PCINC_2 : PCINC_1; +} + +#endif /* KL */ + +/* PI "device" code. */ + +/* Later make these EXTDEFs in kn10def.h and initialize both at runtime */ +int pilev_bits[8] = { + 0, PILEV1, PILEV2, PILEV3, PILEV4, PILEV5, PILEV6, PILEV7 +}; +unsigned char pilev_nums[PILEVS+1] = { 0 }; /* 1st entry zero */ + +/* PI_INIT - Called at startup to initialize PI "device" stuff +*/ +static void +pi_init(void) +{ + register int i; + + /* Set up pilev_nums array, skipping 1st entry which is 0 */ + for (i = 1; i <= PILEVS; ++i) { + register unsigned num = 8, r = i; + while (r) --num, r >>= 1; + pilev_nums[i] = num; + } + + /* Clear PI system */ + cpu.pi.pisys_on = 0; + cpu.pi.pilev_on = 0; + cpu.pi.pilev_pip = 0; + cpu.pi.pilev_preq = 0; + cpu.pi.pilev_dreq = 0; + + /* Clear all device requests too */ + cpu.pi.pilev_aprreq = 0; +#if KLH10_CPU_KS + cpu.pi.pilev_ub1req = 0; + cpu.pi.pilev_ub3req = 0; +#elif KLH10_CPU_KL + cpu.pi.pilev_timreq = 0; + cpu.pi.pilev_rhreq = 0; + cpu.pi.pilev_dtereq = 0; + cpu.pi.pilev_diareq = 0; +#endif +} + +/* RDPI (CONI PI,) - Stores PI status word in E. +** The only PI data subject to asynch update is cpu.pi.pilev_dreq which +** isn't returned in the status, so we don't need to lock anything. +*/ +ioinsdef(io_rdpi) +{ + register w10_t w; + LRHSET(w, cpu.pi.pilev_preq, + ((cpu.pi.pilev_pip< cpu.pi.pilev_pip) ? levs : 0; +} + +#if KLH10_CPU_KS + +/* PI_XCT - Take PI interrupt - KS10 version. +** +** Instruction loop aborted due to PI interrupt on given level. +** Level mask is furnished, as returned by pi_check(). +** Save context, set up new, then return to main loop. +** +** No cleanup needs to be done here as whatever was needed has already +** been done -- this routine is only called from the main loop between +** instructions. The next instruction (what PC points to) might actually +** have just been aborted and backed out of. +** +** Taking an interrupt involves executing the "interrupt vector" +** dispatch instruction for that particular interrupt. This must be +** either a JSR or XPCW (ITS only uses JSR); anything else causes an +** "illegal interrupt" halt. A JSR automatically enters Exec mode; +** an XPCW uses the new flags. +** +** Normally the PI dispatch instruction for level N is found at location +** EPT+040+(2*N) +** However, for an interrupt caused by a Unibus device with vector +** V on adapter C, the PI dispatch is located at: +** c(EPT+0100+C) + (V/4) +** That is, C indexes into the EPT to get a table pointer; V is used +** as an offset from that pointer to get the dispatch instruction location. +** +** NOTE! The vector table address is mapped, not physical! PRM doesn't +** mention this, but this is what TOPS-20 expects. +** +** (NOTE: The DEC Proc Ref Man (6/82 p.4-4) claims adapter #s (1 and 3) +** are not equivalent to the controller #s used in IO addrs (0 and 1). However +** this appears to be wrong and conflicts with the description of IO instrs +** on p. 2-128 of the same manual. Assumption here is controller==adapter.) +** +** Lower-numbered levels have higher priority. +** Within a level, lowest adapter # has highest priority. +** Within an adapter, it appears to be a free-for-all, but presumably +** whichever device is physically closer on the Unibus wins. +** This is represented here by the order in which the devices are +** checked. +** +** Regarding the actual PI level that a Unibus device interrupts on: +** a device may interrupt the Unibus on one of BR4-BR7 (higher number +** has higher priority). BR4&BR5 become KS10 PI requests on the "low" level, +** BR6&BR7 become KS10 PI requests on the "high" level. The actual +** specification of the high and low levels is made by the setting of +** the Unibus Adapter Status register (at UB_UBASTA). +*/ +static void +pi_xct(register int lev) +{ + register w10_t w; + register vaddr_t e; + register paddr_t intvec, pa; /* Note phys addr */ + register int num; + + /* First do a JFFO equivalent to return the PI level number. + ** This hack uses a 128-byte array for speed (32 wds on most machines). + */ + if (!lev || (lev & ~PILEVS)) /* Error checking */ + panic("pi_xct: bad PI level"); + num = pilev_nums[lev]; /* Presto! */ + lev = pilev_bits[num]; /* Get clean single bit */ + + /* Find dispatch info, set up new context, PC, etc. */ + if (lev & cpu.pi.pilev_dreq) { /* Device request? */ + if (lev & cpu.pi.pilev_aprreq) { /* If APR device, do */ + intvec = cpu.mr_ebraddr + EPT_PI0 + (2 * num); /* normal stuff. */ + /* Assume APR interrupt does not automatically turn itself off + ** after being granted, so aprreq remains set. + */ + w = vm_pget(vm_physmap(intvec)); /* Fetch from phys mem */ + } else { + if ((lev & cpu.pi.pilev_ub1req) /* Unibus #1? */ + && (intvec = ub1_pivget(lev))) + pa = EPT_UIT+1; + else if ((lev & cpu.pi.pilev_ub3req) /* Unibus #3? */ + && (intvec = ub3_pivget(lev))) + pa = EPT_UIT+3; + else + panic("pi_xct: Spurious pilev_dreq: %o", cpu.pi.pilev_dreq); + /* If this panic is hit, there is a BUG someplace in the + ** KLH10 code. You can proceed by using the "Set pilev_dreq 0" + ** command, but the bug really should be tracked down. + */ + + pa = vm_pgetrh(vm_physmap(cpu.mr_ebraddr+pa)); /* Get table ptr */ + if (!pa) panic("pi_xct: no PI dev table"); + intvec = pa + (intvec/4); /* Finally derive addr */ + + /* Danger zone -- use exec mapping, can page fault! + ** Uses phys mapping if paging off, will fail if NXM. + */ + if (cpu.mr_paging) { + va_lmake(e, 0, intvec & H10MASK); /* Convert to vaddr_t */ + w = vm_pget(vm_execmap(e, VMF_FETCH)); /* Map and get */ + } else + w = vm_pget(vm_physmap(intvec)); /* Fetch from phys mem */ + } + } else { + /* Must be non-device program-requested PI, do normal stuff */ + /* Find addr of int vector instr */ + intvec = cpu.mr_ebraddr + EPT_PI0 + (2 * num); + w = vm_pget(vm_physmap(intvec)); /* Fetch from phys mem */ + } + + /* Have dispatch vector address and fetched interrupt instr */ + + /* Now do it! + ** Note potential screwiness with EA calculation and mem refs. EA + ** is computed using current context (which might not be EXEC!), and + ** the memory refs are made to exec space with whatever the current AC + ** block is. + ** Also, it is POSSIBLE for the JSR/XPCW to cause a page failure while + ** trying to store the PC word. If this happens, the processor + ** should halt (per DEC Proc Ref Man (12/82) p.4-14 footnote). + ** For the time being we use another silly kludge flag (cpu.mr_inpi). + */ +#if KLH10_DEBUG + if (cpu.mr_debug) { + fe_begpcfdbg(stderr); + } +#endif + switch (iw_op(w)) { + case I_JSR: + /* Interrupt JSR differs from a normal JSR in three ways: + ** (1) Saved PC is as-is, not PC+1 + ** (2) E ref is always to EXEC memory (phys, if paging off) + ** (3) PC flags are munged (in addition to normal clearing) + */ + e = ea_calc(w); /* Figure eff addr (hope I,X not set!) */ + + LRHSET(w, cpu.mr_pcflags, PC_INSECT); /* Cons up PC word */ + cpu.mr_inpi = TRUE; /* Tell pager we're in PI */ + vm_pset(vm_execmap(e,VMF_WRITE), w); /* Store PC word */ + cpu.mr_inpi = FALSE; /* Now out of PI */ + PCFCLEAR(PCF_FPD|PCF_AFI|PCF_TR2|PCF_TR1); /* Clear normally */ +#if KLH10_ITS_1PROC + /* Note that the above PCFCLEAR also clears the PFC_1PR + ** one-proceed flag, in guise of PCF_AFI. This is proper. + */ +#endif + /* Won, now safe to mung processor state and go all the way */ + PCFCLEAR(PCF_USR); /* Switch to exec mode */ + apr_pcfcheck(); /* Make right things happen */ + va_inc(e); /* Increment new PC to E+1 */ + PC_JUMP(e); /* Jump there! */ + break; + + case I_JRST: + if (iw_ac(w) != 07) { /* Verify XPCW (JRST 7,) */ + default: + panic("Illegal Interrupt Halt! Bad int instr: %o,,%o", + LHGET(w), RHGET(w)); + } + /* Handle XPCW. Can't use ij_xpcw() since it checks the flags, + ** and uses current mapping for mem refs. Here, we ignore flags + ** and always use exec mapping once E is obtained. + ** Note mapping is physical if paging off. + */ + { + e = ea_calc(w); /* Figure eff addr (hope I,X not set!) */ + cpu.mr_inpi = TRUE; /* Tell pager we're in PI */ + LRHSET(w, cpu.mr_pcflags, 0); /* Cons up flag word */ + vm_pset(vm_execmap(e,VMF_WRITE), w); /* Store */ + LRHSET(w, 0, PC_INSECT); /* Cons up PC word */ + va_inc(e); /* E+1 */ + vm_pset(vm_execmap(e,VMF_WRITE), w); /* Store */ + va_inc(e); /* E+2 */ + w = vm_pget(vm_execmap(e,VMF_READ)); /* Get flags */ + va_inc(e); /* E+3 */ + pa = vm_pgetrh(vm_execmap(e,VMF_READ)); /* Get PC */ + cpu.mr_inpi = FALSE; /* Now out of PI */ + + /* Won, now safe to mung processor state and go all the way */ + cpu.mr_pcflags = LHGET(w) & PCF_MASK; /* Set new flags */ + apr_pcfcheck(); /* Make right things happen */ + va_lmake(e, 0, pa); /* Get vaddr_t */ + PC_JUMP(e); /* Jump to E */ + } + break; + } + + /* Fix up PI vars to account for taking interrupt */ +#if KLH10_SYS_ITS + if (!cpu.pi.pilev_pip) /* Entering PIP? */ + cpu.pag.pr_quant = /* Yes, no more quantums! */ + quant_freeze(cpu.pag.pr_quant); +#endif /* ITS */ + cpu.pi.pilev_pip |= lev; /* PI now in progress on this level */ +#if KLH10_DEBUG + if (cpu.mr_debug) + fe_endpcfdbg(stderr); +#endif +} +#endif /* KLH10_CPU_KS */ + + +/* PI_DISMISS - Dismiss current PI interrupt. +** No-op if no currently active interrupt. +** Must check for re-interrupting if something else is still +** making a request on the same level. +*/ +void +pi_dismiss(void) +{ + if (cpu.pi.pilev_pip) { +#if KLH10_DEBUG + if (cpu.mr_debug) { + fe_begpcfdbg(stderr); + } +#endif + /* Clear leftmost bit */ + cpu.pi.pilev_pip &= ~pilev_bits[pilev_nums[cpu.pi.pilev_pip]]; + +#if KLH10_SYS_ITS + if (!cpu.pi.pilev_pip) /* No longer PIP? */ + cpu.pag.pr_quant = /* Let 'er rip */ + quant_unfreeze(cpu.pag.pr_quant); +#endif /* ITS */ + if (pi_check()) /* Check for new interrupt to take */ + INSBRKSET(); /* This seems to hang us up?? */ +#if KLH10_DEBUG + if (cpu.mr_debug) { + fprintf(stderr, "(dismiss) "); + pishow(stderr); + putc(']', stderr); + } +#endif + } +} + +#if KLH10_CPU_KL + +/* PI_XCT - Take PI interrupt - KL10 version. +** +** Instruction loop aborted due to PI interrupt on given level. +** Level mask is furnished, as returned by pi_check(). +** Save context, set up new, then return to main loop. +** +** No cleanup needs to be done here as whatever was needed has already +** been done -- this routine is only called from the main loop between +** instructions. The next instruction (what PC points to) might actually +** have just been aborted and backed out of. +** +** Taking an interrupt involves executing the interrupt instruction +** for that particular interrupt. This must be either a JSR or XPCW +** anything else causes an "illegal interrupt" halt. +** (The KL allows BLKI/BLKO but T20 at least doesn't use it.) +** A JSR automatically enters Exec mode; an XPCW uses the new flags. +** +** The KL PI handling is rather complex. See the PRM. +** +** NOTE! The vector table address is mapped, not physical! PRM doesn't +** mention this, but this is what TOPS-20 expects. +** +** Lower-numbered levels have higher priority. +** Within a level, lowest device # has highest priority. +** +*/ +static void +pi_xct(register int lev) +{ + register w10_t w; + register vaddr_t e; + register paddr_t intvec; /* Note phys addr */ + register int num; + register int sect = 0; /* Default section for int instr */ + + /* First do a JFFO equivalent to return the PI level number. + ** This hack uses a 128-byte array for speed (32 wds on most machines). + ** PI level 0 is not supported. + */ + if (!lev || (lev & ~PILEVS)) /* Error checking */ + panic("pi_xct: bad PI level"); + num = pilev_nums[lev]; /* Presto! */ + lev = pilev_bits[num]; /* Get clean single bit */ + + /* Determine source of interrupt. + ** Get its PI function word and derive the interrupt address. + ** The order checked here is that described by PRM p.3-4 + ** Note: + ** In order to handle the situation where programmed requests can + ** be recognized even while their PI level is disabled, without + ** accidentally handling a device request instead, the programmed flags + ** are checked ahead of everything but the interval timer (as per + ** PRM), and the timer check includes a special test to verify that + ** non-program-request PIs are allowed. + */ + if ((lev & cpu.pi.pilev_timreq) /* Check interval timer */ + && (lev & cpu.pi.pilev_on)) { /* Verify enabled */ + /* Interval Timer - internal device. + ** Not clear if PI request is turned off when int is taken, or + ** when TIM_RDONE bit is cleared by int handler. + ** I'll assume it's similar to APR interrupts and leave it on, so + ** the handler must explicitly turn it off by clearing TIM_RDONE. + */ + LRHSET(w, PIFN_FVEC, EPT_ICI); /* Invent bogus fn word */ + intvec = cpu.mr_ebraddr + EPT_ICI; + + } else if ((lev & cpu.pi.pilev_preq) /* Check programmed requests */ + || (lev & cpu.pi.pilev_aprreq)) { /* Check APR request */ + /* Programmed PI requests and APR requests are handled + ** in the same way. They are not automatically turned off when + ** the interrupt is taken, and both use the standard interrupt. + */ + LRHSET(w, 0, 0); /* Use normal dispatch */ + intvec = cpu.mr_ebraddr + EPT_PI0 + (2*num); + + } else if (lev & cpu.pi.pilev_dreq) { + /* External device of some kind. Scan to find which one. + */ + extern struct device *devrh20[8], *devdte20[4]; + register int i; + register struct device *dv = NULL; + + /* First check 8 RH20 channels (maybe rotate order?) */ + if (lev & cpu.pi.pilev_rhreq) { + for (i = 0; i < 8; ++i) { + if ((dv = devrh20[i]) && (dv->dv_pireq & lev)) + break; + } + if (i >= 8) + panic("pi_xct: spurious pilev_rhreq: %o", cpu.pi.pilev_rhreq); + + w = (*(dv->dv_pifnwd))(dv); /* Get its PI funct word */ + LHSET(w, (LHGET(w)&~PIFN_DEV) | (i << 7)); /* Force dev bits */ + intvec = cpu.mr_ebraddr + (RHGET(w) & 0777); + + } else if (lev & cpu.pi.pilev_dtereq) { + /* Then check 4 DTE20s (maybe rotate order?) */ + for (i = 0; i < 4; ++i) { + if ((dv = devdte20[i]) && (dv->dv_pireq & lev)) + break; + } + if (i >= 4) + panic("pi_xct: spurious pilev_dtereq: %o", cpu.pi.pilev_dtereq); + + w = (*(dv->dv_pifnwd))(dv); /* Get its PI funct word */ + LHSET(w, (LHGET(w) & ~PIFN_DEV) | ((i+8)<<7)); + switch (LHGET(w) & PIFN_FN) { + case PIFN_F0: /* Device not interval timer, so do std int */ + case PIFN_FSTD: + intvec = cpu.mr_ebraddr + EPT_PI0 + (2*num); + break; + + case PIFN_FVEC: + /* Special DTE20 code for vector interrupt */ + intvec = cpu.mr_ebraddr + EPT_DT0 + (i<<3) + 2; + break; + + /* Special cases that only DTE20 should be using */ + case PIFN_FINC: + { + register vmptr_t vp; + va_gfrword(e, w); /* Make global virt addr from 13-35 */ + cpu.mr_inpi = TRUE; + vp = vm_execmap(e,VMF_WRITE); + cpu.mr_inpi = FALSE; + if (LHGET(w) & PIFN_Q) + w = vm_pget(vp), op10m_dec(w); + else + w = vm_pget(vp), op10m_inc(w); + vm_pset(vp, w); + return; + } + + case PIFN_FEXA: + case PIFN_FDEP: + case PIFN_FBYT: + panic("pi_xct: DTE20 function not implemented: %lo,,%lo", + (long)LHGET(w), (long)RHGET(w)); + + default: + panic("pi_xct: Illegal function word %lo,,%lo", + (long)LHGET(w), (long)RHGET(w)); + } + + } else if (lev & cpu.pi.pilev_diareq) { + /* Finally check single DIA20 */ + + /* DIA20 is apparently the only device that can ask for + ** a "dispatch interrupt" using bits 13-35 as exec virtual addr. + */ + panic("pi_xct: DIA20 interrupts not supported yet"); + + } else { + + panic("pi_xct: Spurious pilev_dreq: %o", cpu.pi.pilev_dreq); + /* If this panic is hit, there is a BUG someplace in the + ** KLH10 code. You can proceed by using the "Set pilev_dreq 0" + ** command, but the bug really should be tracked down. + */ + } + +#if 0 + /* Determine whether function is special, and execute immediately + ** if so. Distinguish DTE20s from other devices. + */ + switch (LHGET(w) & PIFN_FN) { + case PIFN_F0: /* Device is not interval timer, so do std int */ + case PIFN_FSTD: + intvec = cpu.mr_ebraddr + EPT_PI0 + (2*num); + sect = 0; /* Default section is 0 */ + break; + case PIFN_FVEC: + break; + + /* Special cases that only DTE20 should be using */ + case PIFN_FINC: + case PIFN_FEXA: + case PIFN_FDEP: + case PIFN_FBYT: + default: + panic("pi_xct: Illegal function word %lo,,%lo", + (long)LHGET(w), (long)RHGET(w)); + } + + /* Default section is 0 unless function is "dispatch interrupt", + ** i.e. a vector interrupt from DIA20. + */ + sect = 0; +#endif + + + } else + panic("pi_xct: Spurious int: %o", lev); + + /* Here, have PI function word in w and phys addr of instr in intvec. + ** Standard and vector interrupts come here. + */ + cpu.acblks[7][3] = w; /* Save function word in AC3, BLK7 */ + /* This works as long as BLK7 is not the + ** current AC block - safe enough. + */ + w = vm_pget(vm_physmap(intvec)); /* Fetch int instr from phys mem */ + + + /* Have dispatch vector address and fetched interrupt instr */ + + /* Now do it! + ** Note potential screwiness with EA calculation and mem refs. + ** EA is computed using EXEC context with whatever the current AC block + ** is (which may not be exec ACs), and the default section is normally 0. + ** + ** Also, it is POSSIBLE for the JSR/XPCW to cause a page failure while + ** trying to store the PC word or calculating E. If this happens, + ** the pager should set the "In-out Page Failure" APR flag + ** (per DEC PRM p.3-7 top parag) + ** For the time being we use another silly kludge flag (cpu.mr_inpi). + */ +#if KLH10_DEBUG + if (cpu.mr_debug) { + fe_begpcfdbg(stderr); + } +#endif + switch (iw_op(w)) { + case I_JSR: + /* Interrupt JSR differs from a normal JSR: + ** (1) Saved PC is as-is, not PC+1 + ** (2) E ref is always to EXEC memory + ** (3) PC flags are munged (in addition to normal clearing) + ** (4) PC section test is checked from "sect" instead of PC + ** section. If PC had NZ sect, you lose big. One + ** reason why XPCW is "preferred"! + */ + cpu.mr_inpi = TRUE; /* Tell pager we're in PI */ + e = xea_calc(w, sect); /* Figure EA (hope I,X not set!) */ + + if (sect) + PC_TOWORD(w); /* Use extended PC word, no flags */ + else + LRHSET(w, cpu.mr_pcflags, PC_INSECT); /* Cons up PC word */ + vm_pset(vm_execmap(e,VMF_WRITE), w); /* Store */ + cpu.mr_inpi = FALSE; /* Now out of PI */ + + /* Won, now safe to mung processor state and go all the way */ + PCFCLEAR(PCF_FPD|PCF_AFI|PCF_TR2|PCF_TR1 /* Clear normal flgs*/ + | PCF_USR | PCF_PUB); /* plus User & Public! */ + apr_pcfcheck(); /* Make right things happen */ + va_inc(e); /* Increment new PC to E+1 */ + PC_JUMP(e); /* Jump there! */ + break; + + case I_JRST: + if (iw_ac(w) != 07) { /* Verify XPCW (JRST 7,) */ + default: + panic("Illegal Interrupt Halt! Bad int instr: %o,,%o", + LHGET(w), RHGET(w)); + } + /* Handle XPCW. Can't use ij_xpcw() since it checks the flags, + ** and uses current mapping for mem refs. Here, we ignore flags + ** and always use exec mapping once E is obtained. + ** Note special hacking of PCS for extended KL. + */ +/* Other comments + Note PRM 3-7,3-8 comments on execution in kernel mode, plus + default section of 0 unless function word is a dispatch. + Does XPCW always think it's in kernel mode? Assume yes. This + means it always saves PCS in old flag word, and can set + new flags to whatever it wants. + New flags are simply taken from new-flag word; PCP and PCU are + evidently not treated specially. + PCS doesn't appear to be touched. + KCIO claims that "ucode sets PCS" but I can't find anything + of that sort in the PI code. No mention in PRM. No reason + I can imagine for setting PCS, anyway. +*/ + { + register w10_t npc; + + cpu.mr_inpi = TRUE; /* Tell pager we're in PI */ + e = xea_calc(w, sect); /* Figure EA (hope I,X not set!) */ + LRHSET(w, cpu.mr_pcflags, pag_pcsget()); /* Cons up flag word */ + vm_pset(vm_execmap(e,VMF_WRITE), w); /* Store */ + PC_TOWORD(w); /* Cons up PC word (not PC+1 !!)*/ + va_inc(e); /* E+1 */ + vm_pset(vm_execmap(e,VMF_WRITE), w); /* Store */ + va_inc(e); /* E+2 */ + w = vm_pget(vm_execmap(e,VMF_READ)); /* Get flags */ + va_inc(e); /* E+3 */ + npc = vm_pget(vm_execmap(e,VMF_READ)); /* Get PC */ + cpu.mr_inpi = FALSE; /* Now out of PI */ + + /* Won, now safe to mung processor state and go all the way */ + cpu.mr_pcflags = LHGET(w) & PCF_MASK; /* Set new flags */ + apr_pcfcheck(); /* Make right things happen */ + va_lfrword(e, npc); /* Get 30-bit address from word */ + PC_JUMP(e); /* Jump to E */ + } + break; + +#if 1 /* Diagnostics emulation support */ + /* These instrs are actually UNDEFINED for use as interrupt + ** instructions but the KL diagnostics (DFKAA) expect them to work, + ** so this otherwise unnecessary code is provided to keep diags happy. + ** + ** In the absence of any definition, the algorithms here represent + ** my best guess at their operation. + ** Note that the ACs are whatever the current AC block is. + */ + case I_AOSE: + /* AOSE is done by doing the increment and falling thru to SKIPE. + ** DFKAA doesn't use a NZ AC, so don't bother checking it. + ** No flags (especially no trap flags!) are set. + */ + { + register vmptr_t vp; + + cpu.mr_inpi = TRUE; /* Tell pager we're in PI */ + e = xea_calc(w, sect); /* Figure EA (hope I,X not set!) */ + vp = vm_execmap(e,VMF_WRITE); + cpu.mr_inpi = FALSE; /* Now out of PI */ + w = vm_pget(vp); + op10m_inc(w); /* Increment (don't set flags!) */ + vm_pset(vp, w); /* Store back */ + goto pi_skipe; + } + case I_SKIPE: + /* Interrupt SKIPE differs from a normal SKIPE: + ** DFKAA doesn't use a NZ AC, so don't bother checking it. + ** Skip means interrupt is dismissed. + ** No-skip means 2nd PI location is executed. For DFKAA this is + ** always a JSP, so we fall through (sort of) to handle it. + */ + cpu.mr_inpi = TRUE; /* Tell pager we're in PI */ + e = xea_calc(w, sect); /* Figure EA (hope I,X not set!) */ + w = vm_pget(vm_execmap(e,VMF_READ)); + cpu.mr_inpi = FALSE; /* Now out of PI */ + + pi_skipe: + if (op10m_skipe(w)) { + /* Skipped, so dismiss the interrupt! + ** PIP isn't set -- basically nothing happens! + */ +#if KLH10_DEBUG + if (cpu.mr_debug) + fe_endpcfdbg(stderr); +#endif + return; + } + /* Didn't skip, so handle 2nd PI instr */ + w = vm_pget(vm_physmap(intvec+1)); /* Fetch from phys mem */ + if (iw_op(w) != I_JSP) { + panic("Illegal Interrupt Halt! Bad int instr: %o,,%o", + LHGET(w), RHGET(w)); + } + /* Fall through to handle JSP as 2nd PI location instr */ + + case I_JSP: + /* Interrupt JSP differs from a normal JSP: + ** (1) Saved PC is as-is, not PC+1 + ** (2) E ref is always to EXEC memory + ** (3) PC flags are munged (in addition to normal clearing) + ** (4) PC section test is checked from "sect" instead of PC + ** section. If PC had NZ sect, you lose big. One + ** reason why XPCW is "preferred"! + */ + cpu.mr_inpi = TRUE; /* Tell pager we're in PI */ + e = xea_calc(w, sect); /* Figure EA (hope I,X not set!) */ + cpu.mr_inpi = FALSE; /* Now out of PI */ + num = iw_ac(w); /* Remember AC to use */ + if (sect) + PC_TOWORD(w); /* Use extended PC word, no flags */ + else + LRHSET(w, cpu.mr_pcflags, PC_INSECT); /* Cons up PC word */ + ac_set(num, w); /* Store in AC */ + + /* Won, now safe to mung processor state and go all the way */ + PCFCLEAR(PCF_FPD|PCF_AFI|PCF_TR2|PCF_TR1 /* Clear normal flgs*/ + | PCF_USR | PCF_PUB); /* plus User & Public! */ + apr_pcfcheck(); /* Make right things happen */ + PC_JUMP(e); /* Jump to E! */ + break; + +#endif /* 1 Diagnostics emulation support */ + } + + /* Fix up PI vars to account for taking interrupt */ + cpu.pi.pilev_pip |= lev; /* PI now in progress on this level */ +#if KLH10_DEBUG + if (cpu.mr_debug) + fe_endpcfdbg(stderr); +#endif +} +#endif /* KLH10_CPU_KL */ + +/* Local UUO (LUUO) handling. +** On KS10, just use loc 40 & 41 from whichever space we're in. +** I wonder what happens on a real machine if the instr at 41 screws up? +** This code will already have clobbered loc 40 by then. +** On extended machine, if zero-section just use 40 & 41 as usual. +** If non-Z section, use 4-wd block pointed to by UPT+420 (or EPT+420 +** if exec mode). +** PRM 2-124 appears to be wrong when it asserts that an exec-mode LUUO +** is treated as a MUUO; this is contradicted by Uhler and V7 T20. +** Rather, the behavior is just like user-mode LUUOs except the block +** is pointed to by the EPT instead of UPT. +** Note code in i_xct that duplicates the LUUO code, in order to avoid +** the risk of C stack overflow if the non-extended case encounters +** another LUUO in location 41! +*/ +insdef(i_luuo) +{ + register w10_t w; + register vaddr_t va; + +#if KLH10_EXTADR + if (PC_ISEXT) { + /* Get dispatch vector from physical address of UPT or EPT */ + w = vm_pget(vm_physmap(cpu.mr_usrmode + ? (cpu.mr_ubraddr + UPT_LUU) + : (cpu.mr_ebraddr + EPT_LUU))); + + va_gfrword(va, w); /* Convert word to global vaddr_t */ + + /* Don't need to mung flags since not changing any modes */ + LRHSET(w, cpu.mr_pcflags, ((h10_t)op << 9) | (ac << 5)); + vm_write(va, w); /* Store first loc (flags, opcode) */ + va_ginc(va); + PC_1TOWORD(w); /* Put full PC+1 into word */ + vm_write(va, w); /* Store 2nd loc (PC+1) */ + va_ginc(va); + va_toword_acref(e, w); /* Store E in word, canonicalizing */ + /* for AC ref just like XMOVEI, XHLLI */ + vm_write(va, w); /* Store 3rd loc (E) */ + va_ginc(va); + w = vm_read(va); /* Fetch new PC from 4th loc */ + va_lfrword(va, w); /* Make local vaddr_t from it */ + PC_JUMP(va); + return PCINC_0; + } +#endif /* KLH10_EXTADR */ + + /* Running non-extended, do normal stuff. + ** Note: later could optimize access to 041 knowing it's within page, + ** by using a vmptr_t and incrementing that. + ** If this code is changed, see also the LUUO code in i_xct(). + */ + LRHSET(w, ((h10_t)op<<9)|(ac<<5), /* Put instr together */ + va_insect(e)); + va_lmake(va, 0, 040); /* Get vaddr_t for loc 40 */ + vm_write(va, w); /* Store the word */ + va_linc(va); + return i_xct(I_XCT, 0, va); /* Execute instr at 41 */ +} + + +/* Monitor UUO (MUUO) handling. +** This is also called for any instruction that is illegal +** in user mode. +** +** KLX behavior: +** PCS is saved in the flag word ONLY if old mode was exec. +** PCS is always saved in the saved process context word (UBR). +** The new flags are all cleared except that PCP and PCU are set +** to reflect the old Public and User mode flags. +** Since User is cleared, new mode is always exec. +** New PCS is always set from section # of old PC. +** New PC is masked to either 30 bits (sez PRM) or 23 (sez ucode). +** +** KS behavior: +** It appears that the KS10 restores flags from the PC word vector +** even in T20 mode, just like T10 and single-section KL. +** USER is always cleared, and PCU set to previous value of USER. +*/ +insdef(i_muuo) +{ + register w10_t w; + +#if KLH10_DEBUG + if (cpu.mr_debug) { + fe_begpcfdbg(stderr); + fprintf(stderr,"MUUO %o => ", op); + } +#endif + + /* First set up return PC from MUUO. + ** The PC needs to be bumped by one before being saved, so return + ** will be to the next instruction. + ** + ** If the MUUO is being executed as a trap instruction (as indicated + ** by cpu.mr_intrap) then the trap flags are already clear; upon + ** return, cpu.mr_intrap will be automatically cleared as well. + ** + ** It used to be the case that KLH10 trap instruction execution (trap_xct) + ** didn't touch the PC, thus the MUUO code here had to avoid incrementing + ** the PC for the trap-instruction case. However, the trap code now + ** does a PC decrement so as to fake out all instructions (including + ** MUUO), so no special casing is needed for MUUO's PC increment. + */ +#if 1 + PC_ADD(1); /* Bump PC, so saved PC is a return to after MUUO */ +#else /* Old regime */ + if (!cpu.mr_intrap) /* Unless trapping, bump PC */ + PC_ADD(1); /* so saved PC is return to after MUUO */ +#endif + +#if KLH10_EXTADR + + LRHSET(w, cpu.mr_pcflags, ((h10_t)op<<9)|(ac<<5)); /* Build op wd */ + if (!cpu.mr_usrmode) /* If in exec mode, */ + op10m_iori(w, pag_pcsget()); /* add PCS into op word */ + vm_pset(vm_physmap(cpu.mr_ubraddr + UPT_UUO), w); /* Store op */ + PC_TOWORD(w); + vm_pset(vm_physmap(cpu.mr_ubraddr + UPT_UPC), w); /* Store OPC */ + + /* Set up and store MUUO E. This algorithm is the same one + ** used by XMOVEI. + */ + if (va_iscvtacref(e)) + LRHSET(w, 1, va_insect(e)); /* Global AC reference */ + else + LRHSET(w, va_sect(e), va_insect(e)); + vm_pset(vm_physmap(cpu.mr_ubraddr + UPT_UEA), w); /* Store MUUO E */ + + /* Set up and store current UBR. This includes current PCS + ** before we clobber it. + */ + op10m_tlz(cpu.mr_ubr, UBR_PCS); + op10m_tlo(cpu.mr_ubr, pag_pcsget()&UBR_PCS); + vm_pset(vm_physmap(cpu.mr_ubraddr + UPT_UCX), cpu.mr_ubr); /* Store UBR */ + + /* Now find where new PC will come from, since it depends on + ** the current state -- whether in user mode, and if trapping. + ** For the KL and KI, public mode is also tested. + */ + w = vm_pget(vm_physmap(cpu.mr_ubraddr + + (cpu.mr_usrmode + ? (PCFTEST(PCF_PUB) + ? (cpu.mr_intrap ? UPT_UPT : UPT_UPN) + : (cpu.mr_intrap ? UPT_UUT : UPT_UUN)) + : (PCFTEST(PCF_PUB) + ? (cpu.mr_intrap ? UPT_UST : UPT_USN) + : (cpu.mr_intrap ? UPT_UET : UPT_UEN)) + ))); + + /* Extended MUUO always sets new pager PCS */ + pag_pcsset(PC_SECT); /* Set it from old PC */ + + va_lfrword(e, w); /* Build local vaddr_t from word */ + PC_SET(e); /* Set new PC */ + + /* Now need to build new PC flags. All are cleared, except for + ** PCP and PCU which are set based on the old flags. + ** This appears to be the only place where PCP and PCU are ever set! + */ + cpu.mr_pcflags = (PCFTEST(PCF_PUB) ? PCF_PCP : 0) + | (PCFTEST(PCF_USR) ? PCF_PCU : 0); + +#else /* not KLH10_EXTADR */ + + /* Although MUUO trapping is not logically related to paging, + ** in practice the MUUO algorithm is a function of the pager being + ** used, so the different versions here are selected likewise. + ** Note that a KI10 would need something different from any of this. + */ +#if KLH10_CPU_KLX || (KLH10_CPU_KS && KLH10_PAG_KL) /* KLX or T20 KS */ + LRHSET(w, cpu.mr_pcflags, ((h10_t)op<<9)|(ac<<5)); /* Build op word */ + vm_pset(vm_physmap(cpu.mr_ubraddr+UPT_UUO), w); /* Store op word */ + LRHSET(w, 0, PC_INSECT); + vm_pset(vm_physmap(cpu.mr_ubraddr+UPT_UPC), w); /* Store old PC */ + LRHSET(w, 0, va_insect(e)); + vm_pset(vm_physmap(cpu.mr_ubraddr+UPT_UEA), w); /* Store MUUO E */ + +#else /* KL0, KS/T10, KS/ITS, KI */ + LRHSET(w, ((h10_t)op<<9)|(ac<<5), va_insect(e)); /* Build instr */ + vm_pset(vm_physmap(cpu.mr_ubraddr+UPT_UUO), w); /* Store instr */ + LRHSET(w, cpu.mr_pcflags, PC_INSECT); + vm_pset(vm_physmap(cpu.mr_ubraddr+UPT_UPC), w); /* Store old PC */ +#endif + +#if KLH10_CPU_KL || KLH10_CPU_KS + vm_pset(vm_physmap(cpu.mr_ubraddr+UPT_UCX), cpu.mr_ubr); /* Store UBR */ +#endif + + /* Now find where new PC will come from, since it depends on + ** the current state -- whether in user mode, and if trapping. + ** For the KL and KI, public mode is also tested. + */ +#if KLH10_CPU_KS + w = vm_pget(vm_physmap(cpu.mr_ubraddr + (cpu.mr_usrmode + ? (cpu.mr_intrap ? UPT_UUT : UPT_UUN) + : (cpu.mr_intrap ? UPT_UET : UPT_UEN)) )); + +#elif KLH10_CPU_KI || KLH10_CPU_KL + w = vm_pget(vm_physmap(cpu.mr_ubraddr + + (cpu.mr_usrmode + ? (PCFTEST(PCF_PUB) + ? (cpu.mr_intrap ? UPT_UPT : UPT_UPN) + : (cpu.mr_intrap ? UPT_UUT : UPT_UUN)) + : (PCFTEST(PCF_PUB) + ? (cpu.mr_intrap ? UPT_UST : UPT_USN) + : (cpu.mr_intrap ? UPT_UET : UPT_UEN)) + ))); +#else +# error "KA10 not implemented" +#endif + + PC_SET30(RHGET(w)); /* Set new PC */ + + /* Now need to build new PC flags. Extended KL claims to + ** always clear the flags since the vector has no room for them. + ** Systems/machines which use old-format 18-bit PC & flags + ** do set the flags, however. + ** Not clear if latter case always sets the PCU flag appropriately. + ** For now, assume not. + */ +#if KLH10_CPU_KLX + cpu.mr_pcflags = (cpu.mr_usrmode) ? PCF_PCU : 0; + +#elif KLH10_CPU_KL0 || KLH10_CPU_KS || KLH10_CPU_KI + cpu.mr_pcflags = LHGET(w) & PCF_MASK; +# if KLH10_CPU_KS || KLH10_CPU_KI /* KS ucode always forces PCU */ + if (cpu.mr_usrmode) /* (not sure about KI) */ + cpu.mr_pcflags |= PCF_PCU; + else cpu.mr_pcflags &= ~PCF_PCU; +# endif +#else +# error "KA10 not implemented" +#endif + +#endif /* !KLH10_EXTADR */ + + apr_pcfcheck(); /* Check flags for any changes */ + +#if KLH10_DEBUG + if (cpu.mr_debug) + fe_endpcfdbg(stderr); + if (cpu.mr_exsafe && (PC_INSECT == 0)) { /* Hack to catch T10 bug */ + fprintf(stderr, "[KLH10: MUUO going to 0]"); + if (cpu.mr_exsafe >= 2) + apr_halt(HALT_EXSAFE); + } +#endif + return PCINC_0; +} + + +#if KLH10_ITS_1PROC +/* APR_1PROC - perform one-proceed. Routine is here because it's similar +** to the MUUO code. +*/ +static void +apr_1proc(void) +{ + register w10_t w; + +#if KLH10_DEBUG + if (cpu.mr_debug) { + fe_begpcfdbg(stderr); + fprintf(stderr,"1PROC "); + } +#endif + + /* Execute next instruction. This may actually be a trap instruction, + ** so check for that (duplicating code in apr_check()). + ** The 1-proceed flag needs to be turned off during execution so any + ** saved PC flags don't have it set, but it also needs to be turned + ** back on if the instruction gets a page fault or something. Hence + ** yet another crockish internal flag, cpu.mr_in1proc. + */ + cpu.mr_in1proc = TRUE; /* Say hacking 1-proceed */ + PCFCLEAR(PCF_1PR); /* Then safe to turn off flag */ + if (PCFTEST(PCF_TR1|PCF_TR2) && cpu.mr_paging) + trap_xct(); + else + PC_ADDXCT(i_xct(I_XCT, 0, PC_VADDR)); /* XCT next instr */ + cpu.mr_in1proc = FALSE; /* No longer interruptible */ + + /* Now take the 1-proceed trap! Similar to MUUO handling. */ +/* NOTE: May need to mung PC flags for PCP, PCU! */ + LRHSET(w, cpu.mr_pcflags, PC_INSECT); + vm_pset(vm_physmap(cpu.mr_ubraddr+UPT_1PO), w); /* Store old PC */ + w = vm_pget(vm_physmap(cpu.mr_ubraddr + UPT_1PN)); /* Get new flgs+PC */ + PC_SET30(RHGET(w)); /* Set new PC */ +/* NOTE: May need to mung PC flags for PCP, PCU! */ + cpu.mr_pcflags = LHGET(w) & PCF_MASK; /* and new flags */ + apr_pcfcheck(); /* Check flags for changes */ +#if KLH10_DEBUG + if (cpu.mr_debug) + fe_endpcfdbg(stderr); +#endif +} + +/* A1PR_UNDO - Recover if page-fault or interrupt out of one-proceed. +*/ +void +a1pr_undo(void) +{ + PCFSET(PCF_1PR); /* Turn flag back on */ + cpu.mr_in1proc = FALSE; +} +#endif /* KLH10_ITS_1PROC */ + +#if KLH10_CPU_KI || KLH10_CPU_KL /* AFI Handling */ + +/* APR_AFI - Execute next instruction under Addr-Failure-Inhibit flag. +** Very similar to one-proceed stuff. +** This attempts to emulate the behavior described in PRM 3-52, +** although that is a bit unclear about odd cases like traps. +*/ +static void +apr_afi(void) +{ +#if KLH10_DEBUG + if (cpu.mr_debug) { + fe_begpcfdbg(stderr); + fprintf(stderr,"AFI "); + } +#endif + + /* Execute next instruction. This may actually be a trap instruction, + ** so check for that (duplicating code in apr_check()). + ** The AFI flag needs to be turned off during execution so any + ** saved PC flags don't have it set, but it also needs to be turned + ** back on if the instruction gets a page fault or something. Hence + ** yet another crockish internal flag, cpu.mr_inafi. + */ + /* HOWEVER, one exception to this, unlike the one-proceed case: + ** If taking a trap, leave the flag ON, so it will be saved & cleared + ** if the trap instruction saves it, and otherwise cleared afterwards. + ** This will lose if the trap instruction sets AFI itself, but that + ** seems too improbable to worry about. + */ + cpu.mr_inafi = TRUE; /* Say hacking AFI */ + if (PCFTEST(PCF_TR1|PCF_TR2) && cpu.mr_paging) { /* If trapping, */ + trap_xct(); /* Do trap instr, leave flag on! */ + PCFCLEAR(PCF_AFI); /* Then safe to turn off flag */ + } else { + PCFCLEAR(PCF_AFI); /* Then safe to turn off flag */ + PC_ADDXCT(i_xct(I_XCT, 0, PC_VADDR)); /* XCT next instr */ + } + cpu.mr_inafi = FALSE; /* No longer interruptible */ + + /* Nothing else to do */ +#if KLH10_DEBUG + if (cpu.mr_debug) + fe_endpcfdbg(stderr); +#endif +} + +/* AFI_UNDO - Recover if page-fault or interrupt out of AFI. +*/ +void +afi_undo(void) +{ + PCFSET(PCF_AFI); /* Turn flag back on */ + cpu.mr_inafi = FALSE; +} +#endif /* KLH10_CPU_KI || KLH10_CPU_KL */ + + +insdef(i_illegal) +{ + if (!cpu.mr_usrmode && cpu.mr_exsafe) { + /* Barf just in case, but fall thru to let 10 monitor handle it */ + fprintf(stderr,"[KLH10: Illegal exec mode op, PC = %lo: %o %o,%lo]", + (long)PC_30, op, ac, (long)e); + if (cpu.mr_exsafe >= 2) + apr_halt(HALT_EXSAFE); + } + return i_muuo(op, ac, e); +} diff --git a/src/kn10def.h b/src/kn10def.h new file mode 100644 index 0000000..c983861 --- /dev/null +++ b/src/kn10def.h @@ -0,0 +1,945 @@ +/* KLH10 CPU state and register definitions +*/ +/* $Id: kn10def.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: kn10def.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#ifndef KN10DEF_INCLUDED +#define KN10DEF_INCLUDED 1 + +#ifdef RCSID + RCSID(kn10def_h,"$Id: kn10def.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +#include "osdsup.h" /* Ensure any OS-dependent stuff is available */ + +#include "word10.h" /* Basic PDP-10 word definitions & facilities */ + +#ifndef EXTDEF +# define EXTDEF extern /* Default is to declare (not define) vars */ +#endif + +/* INSBRK - Sum of all interrupt flags. This is invoked whenever something +** (PI interrupt, trap, device I/O, etc) wants to break out of the normal +** instruction execution loop once the current instruction is done. +** See the INTF macros in osdsup.h. +*/ +#define INSBRK_INIT() INTF_INIT(cpu.mr_insbreak) +#define INSBRKSET() INTF_SET(cpu.mr_insbreak) +#define INSBRKTEST() INTF_TEST(cpu.mr_insbreak) +#define INSBRK_ACTBEG() INTF_ACTBEG(cpu.mr_insbreak) +#define INSBRK_ACTEND() INTF_ACTEND(cpu.mr_insbreak) + +/* Accumulator reference definitions */ + +#define AC_BITS 4 +#define AC_N (1<w[0] = ac_get(AC_17), (dp)->w[1] = ac_get(AC_0), 0 )) + +#define ac_dpset(dp,a) ( ac_issafedouble(a) \ + ? ((*ac_mapd(a) = *(dp)), 0) \ + : (ac_set(AC_17, (dp)->w[0]), ac_set(AC_0, (dp)->w[1]), 0) ) +#define ac_dget(a,d) (ac_issafedouble(a) \ + ? (((d) = *ac_mapd(a)),0) \ + : ((d).w[0] = ac_get(AC_17), (d).w[1] = ac_get(AC_0), 0 )) +#define ac_dset(a,d) (ac_issafedouble(a) \ + ? ((*ac_mapd(a) = (d)), 0) \ + : (ac_set(AC_17, (d).w[0]), ac_set(AC_0, (d).w[1]), 0) ) + +/* Memory definitions */ + +/* Get virtual addressing and virtual memory definitions */ + +#include "kn10pag.h" /* Include pager definitions */ + +/* Well, they used to take up a few pages here... */ + +/* Program Counter (PC) macros +** +** All references to and manipulations of PC should use a PC_ macro. +** This permits relatively straightforward experimentation with +** different PC implementations. +*/ +/* Likewise, the type used to hold a PC may be different from that +** for a virtual address, although for the time being they are still +** the same. +** NOTE: Later when questing for speed, attention needs to be given to the +** cases of mr_bkpt and klh10.c's setting of pcva_t variables. +** +** NOTE also: possibility of a vm_PCfetch() macro to fetch c(PC) +** specially (knows PC is local, no vaddr_t conversion, etc). +*/ +typedef vaddr_t pcva_t; /* Type of PC virt addr */ +typedef int pcinc_t; /* Type of all instruction routines */ +# define PCINC_0 0 /* Instr jumped */ +# define PCINC_1 1 /* Instr normal */ +# define PCINC_2 2 /* Instr skipped */ + +/* Macros to obtain a PC attribute. +*/ +#define PC_VADDR cpu.mr_PC /* Get PC as a vaddr_t */ +#define PC_30 va_30(cpu.mr_PC) /* Full 30-bit PC value */ +#define PC_SECT va_sect(cpu.mr_PC) /* PC section # (right-justified) */ +#define PC_SECTF va_sectf(cpu.mr_PC) /* PC section as a field value */ +#define PC_INSECT va_insect(cpu.mr_PC) /* PC in-section part */ +#define PC_ISEXT PC_SECTF /* TRUE if PC in NZ section */ +#define PC_PAGE va_xapage(cpu.mr_PC) /* Full XA page # of PC */ +#define PC_PAGOFF va_pagoff(cpu.mr_PC) +#define PC_AC va_ac(cpu.mr_PC) +#if KLH10_EXTADR +# define PC_ISACREF ((cpu.mr_PC & (VAF_SBAD|VAF_NNOTA))==0) +#else +# define PC_ISACREF ((cpu.mr_PC & VAF_NNOTA)==0) +#endif + +/* Macros to set or change the PC. +** It would be possible to optimize the SETs if all PC refs +** (vm_fetch in particular) always knew it was local-format. +*/ +#define PC_ADD(n) va_ladd(cpu.mr_PC,n) /* Add n to PC (always local) */ +#define PC_SET30(a) va_lmake30(cpu.mr_PC,a) /* Set PC from int w/o JPC */ +#define PC_SET(va) va_lmake30(cpu.mr_PC,va_30(va)) /* " from vaddr_t */ + +#if KLH10_JPC /* Take a jump. Special so can set JPC if desired */ +# define PC_JUMP(e) (cpu.mr_jpc = cpu.mr_PC, PC_SET(e)) +#else +# define PC_JUMP(e) PC_SET(e) +#endif + +/* Special macro to increment PC based on instruction execution. +** This must NOT be replaced by an expression such as +** (PC = (PC+(x)) & H10MASK) +** because C can't guarantee whether PC or (x) is evaluated first! +*/ +#if KLH10_EXTADR +# define PC_ADDXCT(x) { register pcinc_t i__ = (x); if (i__) PC_ADD(i__); } +#else +# define PC_ADDXCT(x) (cpu.mr_PC += (x)) /* For now; fix up later? */ +#endif + +/* Macros for putting PC into a word. +*/ +#define PC_TOWORD(w) LRHSET(w, PC_SECT, PC_INSECT) /* Put PC in wd */ +#define PC_XWDPC1(w,l) LRHSET(w,l,(cpu.mr_PC+1)&H10MASK) /* ,, */ +#define PC_1TOWORD(w) PC_XWDPC1(w, PC_SECT) /* ,, */ +#define PC_F1WORD(w) PC_XWDPC1(w, cpu.mr_pcflags) /* ,, */ + +/* Macro to put "PC-word" in word: PC+1 plus flags if sect 0 +*/ +#if KLH10_EXTADR +# define PC_1WORD(w) (PC_ISEXT ? PC_1TOWORD(w) : PC_F1WORD(w)) +#else +# define PC_1WORD(w) PC_F1WORD(w) +#endif + + +/* Processor Map change macros +** This macro should be invoked whenever something happens to +** change the address space mapping, either the page map or +** AC block selection. +*/ +#if KLH10_PCCACHE +# define PCCACHE_RESET() (cpu.mr_cachevp = NULL) +#else +# define PCCACHE_RESET() +#endif + +/* Processor PC Flag (PCF) macros */ + +#define PCFSET(f) (cpu.mr_pcflags |= (f)) +#define PCFTRAPSET(f) (cpu.mr_pcflags |= (f), INSBRKSET()) +#define PCFCLEAR(f) (cpu.mr_pcflags &= ~(f)) +#define PCFTEST(f) (cpu.mr_pcflags & (f)) + +/* Macro for OP10 facilities (from kn10ops) to use for flag setting */ +#define OP10_PCFSET(f) (cpu.mr_pcflags |= (f), \ + (((f)&(PCF_TR1|PCF_TR2)) ? INSBRKSET() : 0)) + + +/* PDP-10 processor flags +** These defs are the left half bit values of PDP-10 PC flags. +** Unless otherwise specified, all exist on all models. +*/ +#define PCF_ARO 0400000 /* Arithmetic Overflow (or Prev Ctxt Public) */ +#define PCF_CR0 0200000 /* Carry 0 - Carry out of bit 0 */ +#define PCF_CR1 0100000 /* Carry 1 - Carry out of bit 1 */ +#define PCF_FOV 040000 /* Floating Overflow */ +#define PCF_FPD 020000 /* First Part Done */ +#define PCF_USR 010000 /* User Mode */ +#define PCF_UIO 04000 /* User In-Out (or Prev Ctxt User) */ +#define PCF_PUB 02000 /* [KL/KI] Public Mode */ +#define PCF_AFI 01000 /* [KL/KI] Addr Failure Inhibit */ +#define PCF_TR2 0400 /* [KL/KI/KS] Trap 2 (PDL overflow) */ +#define PCF_TR1 0200 /* [KL/KI/KS] Trap 1 (Arith overflow) */ +#define PCF_FXU 0100 /* Floating Exponent Underflow */ +#define PCF_DIV 040 /* No Divide */ +#define PCF_MASK (H10MASK&~037) /* Can't mung low 5 bits, save for I(X) */ + +/* Duplicate flag meanings in exec mode */ +#define PCF_PCP PCF_ARO /* [KL/KI] Previous Context Public */ +#define PCF_PCU PCF_UIO /* [KL/KI/KS] Previous Context User */ +#if KLH10_ITS_1PROC +# define PCF_1PR PCF_AFI /* KS10 One-proceed flag - re-use AFI */ +#else +# define PCF_1PR 0 +#endif + +/* Instruction opcode macros and defs */ + +/* These cannot come earlier because they depend on some typedefs such +** as vaddr_t and pcinc_t. +*/ + +#include "opdefs.h" /* PDP-10 instruction opcodes and declarations */ + /* Includes IW_ facilities */ + + +/* Processor/microcode configuration */ + +/* APRID bit definitions. +** It appears that some monitors actually pay attention to these bits. +** While the KS and KL have superficially similar APRID fields, the +** meanings of the bits are completely different, so each needs its +** own specific definitions. +*/ + +/* KS notes: +** The PRM originally had no option bits defined; all of the options +** defined below were later additions. +*/ +#if KLH10_CPU_KS +# define AIF_UCOPT 0777000 /* LH: Microcode options field */ +# if KLH10_SYS_ITS +# define AIF_ITS 020000 /* ITS ucode [ITS KS10 only!] */ +# else +# define AIF_INHCST 0400000 /* "Inhibit CST update" supported */ +# define AIF_NOCST 0200000 /* No CST */ +# define AIF_SEX 0100000 /* "Exotic uCode" (ie non-standard) */ +# define AIF_UBLT 040000 /* BLTUB, BLTBU supported */ +# define AIF_KIPG 020000 /* KI paging (old T10) */ +# define AIF_KLPG 010000 /* KL paging (T20) */ +# endif +# define AIF_UCVER 0777 /* LH: ucode version # field */ +# define AIF_HWOPT 0700000 /* RH: Hardware options field */ +# define AIF_SNO 077777 /* RH: Processor serial # (15 bits!) */ + +/* Default the ucode version and APR serial number */ + +# ifndef KLH10_APRID_UCVER +# if KLH10_SYS_ITS +# define KLH10_APRID_UCVER 0262 /* Last ITS KS ucode version */ +# else +# define KLH10_APRID_UCVER 0130 /* Last DEC KS ucode version? */ +# endif +# endif +# ifndef KLH10_APRID_SERIALNO +# define KLH10_APRID_SERIALNO 4097 /* This is popular for some reason */ +# endif /* (maybe cuz an impossible SN for a KL) */ + +#endif /* KS */ + + +/* KL notes: +** Most of the KL bits are documented in the PRM, but +** AIF_KLB, AIF_PMV, and AIF_MCA were later additions. +** When ITS ran on a KL10A, it used the same bit as the KS10 (AIF_ITS) +** to indicate it was non-standard ucode. But there's no need to +** resurrect MC, so don't worry about it. +*/ +#if KLH10_CPU_KL +# define AIF_UCOPT 0777000 /* LH: Microcode options field */ +# define AIF_T20 0400000 /* 0 UCode supports T20 paging */ +# define AIF_EXA 0200000 /* 1 uCode supports Extended Addressing */ +# define AIF_SEX 0100000 /* 2 "Exotic uCode" (ie non-standard) */ +# define AIF_KLB 040000 /* 3 KL10B CPU (?) */ +# define AIF_PMV 020000 /* 4 PMOVE/PMOVEM */ +# define AIF_VER 0777 /* LH: ucode version # field */ + +# define AIF_HWOPT 0770000 /* RH: Hardware options field */ +# define AIF_50H 0400000 /* 18 50Hz power */ +# define AIF_CCA 0200000 /* 19 Cache */ +# define AIF_CHN 0100000 /* 20 "Channel" - has RH20s? */ +# define AIF_KLX 040000 /* 21 Extended KL10, else Single-section KL */ +# define AIF_OSC 020000 /* 22 "Master Oscillator" (golly) */ +# define AIF_MCA 010000 /* 23 MCA25 Keep Bit (per MCA25 doc p.A-10) */ +# define AIF_SNO 07777 /* RH: Hardware Serial Number (only 12 bits) */ + +/* Default the ucode version and APR serial number */ + +# ifndef KLH10_APRID_UCVER +# if KLH10_SYS_ITS +# define KLH10_APRID_UCVER 02-- /* Last ITS KL ucode version? */ +# else +# define KLH10_APRID_UCVER 0442 /* Last DEC KL ucode version? */ +# endif +# endif +# ifndef KLH10_APRID_SERIALNO +# define KLH10_APRID_SERIALNO 759 /* Well, what else to use? */ +# endif + +#endif /* KL */ + +/* APR "device" definitions +*/ + +struct aprregs { + int aprf_set; /* APR flag settings & chan # */ + int aprf_ena; /* APR flags enabled for interrupt */ + int aprf_lev; /* APR level bit to interrupt on */ +}; + +/* APR device flags. Note these all fit within 16 bits on the KS. +** The KL is a bit messier but can still be managed. +*/ +#if KLH10_CPU_KL +# define APRW_IORST 0200000 /* 19 W: Clear all external I/O devices */ +# define APRR_SWPBSY 0200000 /* 19 R: Sweep busy */ +#endif +#define APRW_ENA 0100000 /* 20 Enable ints on selected flags */ +#define APRW_DIS 040000 /* 21 Disable ints on selected flags */ +#define APRW_CLR 020000 /* 22 Clear flags */ +#define APRW_SET 010000 /* 23 Set flags */ +#define APRF_MASK 07760 /* Flag mask */ +#if KLH10_CPU_KL +# define APRF_SBUS 04000 /* 24 S-Bus Error */ +# define APRF_NXM 02000 /* 25 No Memory (locks ERA) */ +# define APRF_IOPF 01000 /* 26 IO Page Failure */ +# define APRF_MBPAR 0400 /* 27 MB Parity (locks ERA) */ +# define APRF_CDPAR 0200 /* 28 Cache Dir Parity */ +# define APRF_ADPAR 0100 /* 29 Address Parity (locks ERA) */ +# define APRF_PWR 040 /* 30 Nuclear Parity (ok, Power Failure) */ +# define APRF_SWPDON 020 /* 31 Sweep Done */ +#elif KLH10_CPU_KS +# define APRF_24 04000 /* 24 Unused? */ +# define APRF_INT80 02000 /* 25 Interrupt 8080 FE when set */ +# define APRF_PWR 01000 /* 26 Power failure */ +# define APRF_NXM 0400 /* 27 Non-ex memory */ +# define APRF_BMD 0200 /* 28 Bad Memory data */ +# define APRF_ECC 0100 /* 29 Corrected memory data */ +# define APRF_TIM 040 /* 30 Timer Interval done */ +# define APRF_FEINT 020 /* 31 Interrupt from 8080 FE */ +#endif +#define APRR_INTREQ 010 /* Something's requesting an int */ +#define APRF_CHN 07 /* Mask for APR channel */ + + +/* PI System definitions +** +** Terminology note: ITS still uses the word "channel" to mean the same thing +** as what DEC now calls "level". +*/ + +/* PI device flags, written by CONO PI, (KS: WRPI) +** Note all fit within 16 bits, except for high-order KL bits that +** aren't really part of the PI system. +*/ +#if KLH10_CPU_KL +# define PIF_WEPADR 0400000 /* Write Even Parity - Address */ +# define PIF_WEPDAT 0200000 /* Write Even Parity - Data */ +# define PIF_WEPDIR 0100000 /* Write Even Parity - Directory */ +#endif +#define PIW_LDRQ 020000 /* Drop IRQs on selected levels */ +#define PIW_CLR 010000 /* Clear PI system */ +#define PIW_LIRQ 04000 /* Initiate interrupt on selected levels */ +#define PIW_LON 02000 /* Turn on selected levels (enable) */ +#define PIW_LOFF 01000 /* Turn off " " (disable) */ +#define PIW_OFF 0400 /* Turn off PI system */ +#define PIW_ON 0200 /* Turn on " " */ +#define PILEVS 0177 /* Mask for all PI level select bits */ +#define PILEV1 0100 /* Bits used to select particular PI levels */ +#define PILEV2 040 +#define PILEV3 020 +#define PILEV4 010 +#define PILEV5 04 +#define PILEV6 02 +#define PILEV7 01 + +/* Flags read by CONI PI, (KS: RDPI) +** LH 0177 bits contain levels with program requests active (preq) +** RH 0177 bits contain levels turned on (enabled) +** RH 0177<<8 contain levels being held (PI In Progress) +*/ +#define PIR_PIPSHIFT 8 /* Shift arg to find PIP bits in RH */ +#define PIR_ON PIW_ON /* PI system on */ + +#if KLH10_CPU_KL + /* PI Function Word fields */ +#define PIFN_ASP 0700000 /* LH: Address space to use for fns 4 & 5 */ +#define PIFN_ASPEPT 0 /* Exec Process Table */ +#define PIFN_ASPEVA 0100000 /* Exec virtual address */ +#define PIFN_ASPPHY 0400000 /* Physical address */ + +#define PIFN_FN 070000 /* LH: Function code */ +#define PIFN_F0 0 /* Internal device or zero word */ +#define PIFN_FSTD 010000 /* Standard interrupt EPT+(40+2N) */ +#define PIFN_FVEC 020000 /* Vector interrupt (dev/adr) */ +#define PIFN_FINC 030000 /* Increment */ +#define PIFN_FEXA 040000 /* DTE20 Examine */ +#define PIFN_FDEP 050000 /* DTE20 Deposit */ +#define PIFN_FBYT 060000 /* DTE20 Byte Transfer */ +#define PIFN_FAOS 070000 /* IPA20-L (NIA20, CI) inc & return val */ + +#define PIFN_Q 04000 /* LH: Q bit */ +#define PIFN_DEV 03600 /* LH: Physical Device # */ +#define PIFN_LHADR 037 /* LH: High 5 bits of address */ + /* RH: Low 18 bits of address */ + +#endif /* KL */ + +struct piregs { + int pisys_on; /* PIR_ON bit set if PI system on, else 0 */ + int pilev_on; /* Levels active (enabled) - can take ints */ + int pilev_pip; /* Levels holding ints (PI in Progress) */ + + int pilev_preq; /* Levels to initiate prog reqs on */ + /* (these take effect even if pilev_on is off!) */ + + int pilev_dreq; /* Device PI requests outstanding */ + int pilev_aprreq; /* "Devices" in APR */ +#if KLH10_CPU_KL + int pilev_timreq; /* Interval timer PI request */ + int pilev_rhreq; /* RH20 device requests */ + int pilev_dtereq; /* DTE20 device requests */ + int pilev_diareq; /* DIA20 device requests */ + +#elif KLH10_CPU_KS + int pilev_ub1req; /* Devices on UBA #1 */ + int pilev_ub3req; /* Devices on UBA #3 */ +#endif +}; + +/* Defined & initialized in kn10cpu.c PI code; later make EXTDEFs */ +extern int pilev_bits[]; /* Bit masks indexed by PI level number */ +extern unsigned char pilev_nums[]; /* PI level nums indexed by PI bit mask */ + +/* Timing system definitions */ + +#if KLH10_CPU_KL + +/* Bits for CONO MTR, (those marked [I] are also read on CONI) +*/ +# define MTR_SUA 0400000 /* Set Up Accounts */ +# define MTR_AEPI 040000 /* [I] Acct: Executive PI Account */ +# define MTR_AENPI 020000 /* [I] Acct: Executive Non-PI Account */ +# define MTR_AON 010000 /* [I] Acct: Turn on */ +# define MTR_TBON 04000 /* [I] Time Base: turn on */ +# define MTR_TBOFF 02000 /* Time Base: turn off */ +# define MTR_TBCLR 01000 /* Time Base: clear */ +# define MTR_ICPIA 07 /* [I] Interval Counter: PI Assignment */ + +/* Bits for CONO TIM, +*/ +# define TIM_WCLR 0400000 /* Clear Interval Counter */ +# define TIM_ON 040000 /* Turn on Interval Counter */ +# define TIM_WFCLR 020000 /* Clear Interval Flags */ +# define TIM_RDONE 020000 /* Interval Done */ +# define TIM_ROVFL 010000 /* Interval Overflow */ +# define TIM_PERIOD 07777 /* Interval Period */ +/* Note: LH of CONI has current interval counter in its TIM_PERIOD field */ + +/* According to the PRM, all hardware counters are 16 bits except +** the interval counter (12 bits). +** +** All "doubleword" counts are 59-bit unsigned quantities with the +** hardware counter at the low end. +** The representation in a PDP-10 doubleword has the high 36 bits +** in the high-order word, and the low 23 in bits 1-23 of the low word, +** with bit 0 zero. +** The low 12 bits of the doubleword are reserved for +** extensibility to future faster machines (!). +*/ + +#endif /* KL */ + + +struct timeregs { + +#if KLH10_CPU_KS + w10_t tim_intreg; /* Interval Time reg set by WRINT */ + + uint32 tim_base[3]; /* 71-bit time base, native form */ + dw10_t wrbase; /* Time Base of last WRTIM */ + osrtm_t osbase; /* OS Time Base of last WRTIM */ + +#elif KLH10_CPU_KL + unsigned mtr_flgs; /* MTR condition flags */ + int mtr_on; /* TRUE if accounting on */ + int mtr_ifexe; /* TRUE to include exec non-PI */ + int mtr_ifexepi; /* TRUE to include exec PI */ + uint32 mtr_eact; /* Execution acct hardware cntr */ + uint32 mtr_mact; /* Mem ref acct hardware cntr */ + + int tim_on; /* TRUE if interval counter on */ + int tim_lev; /* PI level bit to interrupt on, if any */ + unsigned int tim_flgs; /* TIM condition flags and PIA */ + unsigned int tim_intper; /* Interval counter period */ + unsigned int tim_intcnt; /* Interval countdown */ + + int tim_tbon; /* Time base on/off flag */ + uint32 tim_tbase; /* Time base hardware cntr */ + osrtm_t tim_osbase; /* OS Time Base of last WRTIME reset */ + + uint32 tim_perf; /* Performance analysis hardware cntr */ +#endif +}; + +#define TIMEBASEFILE "APR.TIMEBASE" /* Change this name later */ + +/* Determine whether any clock countdown is needed at all */ +#define IFCLOCKED (KLH10_RTIME_SYNCH || KLH10_ITIME_SYNCH || KLH10_QTIME_SYNCH) +#define IFPOLLED (!KLH10_CTYIO_INT || !KLH10_IMPIO_INT) + +#include "kn10clk.h" /* New clock stuff */ + + +/* Stuff needed for ITS pager quantum counter, used by both kn10cpu and kn10pag. + See time code comments in KN10CPU. +*/ +#if KLH10_SYS_ITS +# if KLH10_QTIME_SYNCH +# define quant_unfreeze(q) ((q) + (CLK_USEC_UNTIL_ITICK()<<2)) +# define quant_freeze(q) ((q) - (CLK_USEC_UNTIL_ITICK()<<2)) +# elif KLH10_QTIME_OSREAL || KLH10_QTIME_OSVIRT + extern int32 quant_freeze(int32), quant_unfreeze(int32); +# endif +#endif + +/* UPT/EPT definitions +*/ + +/* UPT Offsets +** ITS: In non-time sharing and at clock level, UPT=EPT. +*/ + +#if KLH10_PAG_KI +# define UPT_PMU 0 /* 000-377: T10 User page map table (pages 0-777) */ +# define UPT_PME340 0400 /* 400-417: T10 Exec page map table (pages 340-377) */ +#endif + +#if KLH10_CPU_KI +# define UPT_PFT 0420 /* User page failure trap instr */ +#elif KLH10_CPU_KLX +# define UPT_LUU 0420 /* 30-bit addr of LUUO dispatch block */ +#endif +#define UPT_TR1 0421 /* User mode arith ovfl trap. */ +#define UPT_TR2 0422 /* User mode pdl ov trap. */ +#define UPT_TR3 0423 /* User mode trap 3 in non-one-proceed microcode. */ + + /* Although MUUO trapping is not logically related to paging, + ** in practice the MUUO algorithm is a function of the pager being + ** used, as well as the CPU, so the different versions here are + ** selected likewise. These categories correspond to those in + ** PRM 2-126 (fig 2.3) + */ +#if KLH10_CPU_KLX || (KLH10_CPU_KS && KLH10_PAG_KL) /* KLX or T20 KS */ +# define UPT_UUO 0424 /* MUUO PC flags, opcode, A, and PCS stored here. */ +# define UPT_UPC 0425 /* MUUO old PC */ +# define UPT_UEA 0426 /* MUUO Effective Address */ +# define UPT_UCX 0427 /* MUUO process context (from RDUBR) */ +#elif KLH10_CPU_KL0 && KLH10_PAG_KL /* T20 KL0 */ +# define UPT_UUO 0425 /* MUUO opcode, A, PC and Eff Addr */ +# define UPT_UPC 0426 /* MUUO old PC and flags */ +# define UPT_UCX 0427 /* MUUO process context (from RDUBR) */ +#elif (KLH10_CPU_KL0 || KLH10_CPU_KS) && (KLH10_PAG_KI || KLH10_PAG_ITS) +# define UPT_UUO 0424 /* MUUO opcode, A, PC and Eff Addr */ +# define UPT_UPC 0425 /* MUUO old PC and flags */ +# define UPT_UCX 0426 /* MUUO process context (from RDUBR) */ +#elif KLH10_CPU_KI +# define UPT_UUO 0424 /* MUUO opcode, A, PC and Eff Addr */ +# define UPT_UPC 0425 /* MUUO old PC and flags */ +#endif + + +#define UPT_UEN 0430 /* MUUO new PC - Exec mode, no trap */ +#define UPT_UET 0431 /* MUUO new PC - Exec mode, trap instr */ +#if KLH10_SYS_ITS && KLH10_CPU_KS +# define UPT_1PO 0432 /* One-proceed old PC stored here (if 1proc ucode) */ +# define UPT_1PN 0433 /* One-proceed new PC obtained from here (if " ) */ +#elif KLH10_CPU_KI || KLH10_CPU_KL +# define UPT_USN 0432 /* MUUO new PC - Supv mode, no trap */ +# define UPT_UST 0433 /* MUUO new PC - Supv mode, trap instr */ +#endif +#define UPT_UUN 0434 /* MUUO new PC - User mode, no trap */ +#define UPT_UUT 0435 /* MUUO new PC - User mode, trap instr */ +#if KLH10_CPU_KI || KLH10_CPU_KL +# define UPT_UPN 0436 /* MUUO new PC - Public mode, no trap */ +# define UPT_UPT 0437 /* MUUO new PC - Public mode, trap instr */ +#endif + + +#if KLH10_CPU_KS || KLH10_CPU_KL +# if KLH10_PAG_KI /* T10 paging */ +# define UPT_PFW 0500 /* Page Fail Word */ +# define UPT_PFO 0501 /* Page Fail Old PC+flags word */ +# define UPT_PFN 0502 /* Page Fail New PC+flags word */ +# elif KLH10_PAG_KL /* T20 paging */ +# define UPT_SC0 0540 /* T20 User Section 0 pointer */ +# if KLH10_CPU_KS || KLH10_CPU_KLX +# define UPT_PFW 0500 /* Page Fail Word */ +# define UPT_PFF 0501 /* Page Fail Flags */ +# define UPT_PFO 0502 /* Page Fail Old PC */ +# define UPT_PFN 0503 /* Page Fail New PC */ +# elif KLH10_CPU_KL0 +# define UPT_PFW 0501 /* Page Fail Word */ +# define UPT_PFO 0502 /* Page Fail Old PC+flags word */ +# define UPT_PFN 0503 /* Page Fail New PC+flags word */ +# endif +# endif +#endif /* KS+KL */ + +#if KLH10_CPU_KL +# define UPT_PXT 0504 /* User Process Execution Time (doubleword) */ +# define UPT_MRC 0506 /* User Memory Reference Count (doubleword) */ +#endif + + +/* EPT Locations */ + +#if KLH10_CPU_KL +# define EPT_CL0 0 /* Channel Logout Area 0 (of 8) (quadword) */ +#endif +#if KLH10_CPU_KI +# define EPT_UUO 040 /* Exec LUUO stored here */ +# define EPT_UUH 041 /* Exec LUUO handler instruction */ +#endif +#define EPT_PI0 040 /* PI0LOC+2*PICHN = Addr of instr pair for PICHN. */ + /* Note KL has a PI0! Otherwise, locs */ + /* actually used are 42-57 inclusive. */ +#if KLH10_CPU_KL +# define EPT_CB0 060 /* Channel Block Fill Word 0 (of 4) */ +#endif + +#if KLH10_CPU_KS +# define EPT_UIT 0100 /* EPT_UIT+N contains addr of the interrupt */ + /* table for unibus adapter N. Only */ + /* adapters 1 and 3 ever exist. */ +#endif /* KS */ + +#if KLH10_CPU_KL +# define EPT_DT0 0140 /* DTE20 Control Block 0 (of 4) (8 wds each) */ +#endif +#if KLH10_PAG_KI +# define EPT_PME400 0200 /* 200-377: T10 Exec page map table (pages 400-777) */ +#endif +#if KLH10_CPU_KLX +# define EPT_LUU 0420 /* Exec mode 30-bit LUUO block location */ +#elif KLH10_CPU_KI +# define EPT_PFT 0420 /* Exec page failure trap instr */ +#endif +#define EPT_TR1 0421 /* Exec mode arith ovfl trap. */ +#define EPT_TR2 0422 /* Exec mode pdl ov trap. */ +#define EPT_TR3 0423 /* Exec mode trap 3 (1 proceed?). */ + +#if KLH10_SYS_ITS +/* + In the ITS microcode the three words used to deliver a page fail are + determined from the current interrupt level. At level I, the page fail + word is stored in EPTPFW+<3*I>, the old PC is stored in EPTPFO+<3*I>, + and the new PC is obtained from EPTPFN+<3*I>. If no interrupts are in + progress we just use EPTPFW, EPTPFO and EPTPFN. +*/ +#define EPT_PFW 0440 /* Page fail word stored here. */ +#define EPT_PFO 0441 /* Page fail old PC stored here. */ +#define EPT_PFN 0442 /* Page fail new PC obtained from here. */ +#endif /* ITS */ + +#if KLH10_CPU_KL +/* The PRM doesn't mention this, but it appears that TOPS-20 (at least) uses +** EPT locations 444-457 inclusive as a special communication area with +** the Master DTE. See dvdte.h for more details. +*/ +#endif + +#if KLH10_CPU_KL +# define EPT_TBS 0510 /* Time Base (doubleword) */ +# define EPT_PRF 0512 /* Performance Analysis Counter (doubleword) */ +# define EPT_ICI 0514 /* Interval Counter Interrupt Instruction */ +#endif + +#if KLH10_PAG_KL +# define EPT_SC0 0540 /* T20 Exec Section 0 pointer */ +#endif +#if KLH10_PAG_KI +# define EPT_PME0 0600 /* 600-757: T10 Exec page map table (pages 0-337) */ + /* (note pages 340-377 are in UPT!) */ +#endif + +/* FE (console) stuff needed for interaction */ + +/* Halt codes returned to FE when KLH10 CPU stops. +*/ +enum haltcode { /* Must not be zero */ + HALT_PROG=1, /* Program halt (JRST 4,) */ + HALT_FECTY, /* FE Console interrupt */ + HALT_BKPT, /* Hit breakpoint */ + HALT_STEP, /* Single-Stepping */ + HALT_EXSAFE, /* Badness in exec mode */ + HALT_PANIC /* Panic - internal error, bad state */ +}; + +struct feregs { + int fe_ctyinp; /* # chars CTY input waiting, if any */ + int fe_intchr; /* If non-zero, FE interrupt/cmd escape char */ + int fe_intseen; /* TRUE if FE CTY interrupt seen */ + int fe_debug; /* TRUE to print debug info */ + +#if KLH10_CPU_KS + int fe_iowait; /* # usec clock ticks to delay I/O response */ +# if KLH10_CTYIO_ADDINT + int cty_lastint; /* # 1ms ticks after last output to give int */ + int cty_prevlastint; /* Previous value of above */ + struct clkent *cty_lastclk; /* Clock timer for last-output int */ +# endif +#endif /* KLH10_CPU_KS */ +}; + +#if KLH10_CPU_KS + +/* FE <-> KS10 Communications Area locations */ +#define FECOM_SWIT0 030 /* Simulated switch 0. Set by 8080 SH cmd. */ +#define FECOM_KALIV 031 /* Keep Alive & Status. */ +#define FECOM_CTYIN 032 /* CTY input. */ +#define FECOM_CTYOT 033 /* CTY output. */ +#define FECOM_KLKIN 034 /* KLINIK user input word (from 8080). */ +#define FECOM_KLKOT 035 /* KLINIK user output word (to 8080). */ +#define FECOM_RHBAS 036 /* BOOT RH11 base address. */ +#define FECOM_QNUM 037 /* BOOT Unit Number. */ +#define FECOM_BOOTP 040 /* Magtape Boot Format and Slave Number. */ + +#endif /* KS */ + +#if KLH10_CPU_KL /* Low core locs checked by T20 scheduler */ +# define FECOM_SWIT0 030 /* SHLTW - Halt request if NZ, see SCHED.MAC */ + /* 020 */ /* SCTLW - Request word, various bits */ +#endif + +/* Global Machine state variables +** This is a structure so references to the contents can all be made +** as offsets from a base address (which may be in a register). On +** some architectures such as the SPARC this allows a 1-instruction +** reference as opposed to the usual 3-instruction sequence for a +** random global reference. +** On other architectures it doesn't matter but doesn't hurt either. +*/ + +struct machstate { + /* Current ACs; at start of struct to make indexing faster. + ** This block is swapped with cpu.acblks[n] + ** whenever the current AC block changes. + */ + acblk_t acs; + int mr_acbcur; /* AC block # of current acs above */ + + /* General internal machine registers */ + h10_t mr_pcflags; /* Current PC flags */ + pcva_t mr_PC; /* Current virtual PC */ +#if KLH10_PCCACHE + vmptr_t mr_cachevp; /* Current cached PC map pointer */ +#endif +#if KLH10_JPC + pcva_t mr_jpc; /* Virtual PC of last jump instruction */ + pcva_t mr_ujpc; /* Last JPC when user mode left */ + pcva_t mr_ejpc; /* Last JPC when exec mode left */ +#endif +#if KLH10_CPU_KS + w10_t mr_hsb; /* Halt Status Block base address */ +#elif KLH10_CPU_KI || KLH10_CPU_KL + w10_t mr_adrbrk; /* Address Break register */ +#endif + w10_t mr_aprid; /* Processor ID */ + w10_t mr_ebr; /* Executive Base Register */ + w10_t mr_ubr; /* User Base Register */ + paddr_t mr_ebraddr, mr_ubraddr; /* Word addresses for above */ + int mr_paging; /* TRUE if paging & traps enabled */ + int mr_usrmode; /* TRUE if in USER mode (else EXEC mode) */ + int mr_inpxct; /* TRUE if executing PXCT operand */ +#if KLH10_CPU_KLX + pcva_t mr_pxctpc; /* Saved PC if PXCT bit 12 set */ +#endif + h10_t mr_intrap; /* NZ (Trap flags set) if XCTing trap instr */ +#if KLH10_ITS_1PROC + int mr_in1proc; /* TRUE if executing one-proceed instr */ +#elif KLH10_CPU_KI || KLH10_CPU_KL + int mr_inafi; /* TRUE if executing AFI instruction */ +#endif + int mr_injrstf; /* TRUE if executing JRSTF/JEN */ + int mr_inpi; /* TRUE if executing PI instruction */ + + osintf_t mr_insbreak; /* See INSBRKSET, INSBRKTEST */ + osintf_t intf_fecty; /* For FE CTY interrupts */ +#if KLH10_EVHS_INT + osintf_t intf_evsig; /* For event-reg signals */ +#endif + osintf_t intf_ctyio; /* For CTY I/O */ + osintf_t intf_impio; /* For SIMP (LH/DH) I/O */ + osintf_t intf_clk; /* Clock interval interrupt */ + + vmptr_t physmem; /* Ptr to physical memory area */ + struct acregs acblk; /* AC block mappings */ + struct vmregs vmap; /* Pager context mappings */ + struct pagregs pag; /* Paging registers */ + struct aprregs aprf; /* APR device stuff */ + struct piregs pi; /* PI system stuff */ + struct timeregs tim; /* Timer and clock stuff */ + struct clkregs clk; /* Internal Clock stuff */ + w10_t mr_dsw; /* Console Data Switches */ + + /* Debugging stuff */ + int mr_debug; /* TRUE to print general CPU debug info */ + int mr_dotrace; /* TRUE if doing execution trace */ + int mr_1step; /* TRUE to stop after next instr */ + int mr_exsafe; /* NZ does exec-mode safety checks; 2 = halt */ + pcva_t mr_bkpt; /* Non-zero to stop when mr_PC == mr_bkpt */ + pcva_t mr_haltpc; /* PC of a halt instruction */ + + /* Miscellaneous cruft */ + int mm_shared; /* TRUE if using shared phys memory */ + int mm_locked; /* TRUE if want memory locked */ + osmm_t mm_physegid; /* Phys memory shared segment ID (can be 0) */ + struct feregs fe; /* FE stuff */ +#if KLH10_CPU_KS + int io_ctydelay; /* Ugh. To emulate I/O device delays. */ +#endif + + /* Bulk storage, at end so it's less annoying when debugging. */ + + w10_t acblks[ACBLKS_N][16]; /* Actual AC blocks! 16 wds each */ + opfp_t opdisp[I_N]; /* I_xxx Routine dispatch table */ + pment_t pr_umap[PAG_MAXVIRTPGS]; /* Internal user mode map table */ + pment_t pr_emap[PAG_MAXVIRTPGS]; /* " exec " " " */ +}; + +EXTDEF struct machstate cpu; + + +/* Processor function declarations - from kn10cpu.c */ + +extern void apr_picheck(void); /* Check for pending PI */ +extern void apr_pcfcheck(void); /* Check PC flags for side effects */ +extern void apr_int(void); /* Invoke interrupt system */ +extern void apr_halt(enum haltcode); /* Halt processor */ +extern void pi_dismiss(void); /* Dismiss current interrupt */ +#if KLH10_CPU_KL +extern void mtr_update(void); /* Update accounts */ +#endif + +/* Finally, declare panic routine to call when all else fails. */ + +extern void panic(char *, ...); + +#endif /* ifndef KN10DEF_INCLUDED */ diff --git a/src/kn10dev.c b/src/kn10dev.c new file mode 100644 index 0000000..f4458d3 --- /dev/null +++ b/src/kn10dev.c @@ -0,0 +1,1980 @@ +/* KN10DEV.C - KLH10 Generic Device Support +*/ +/* $Id: kn10dev.c,v 2.4 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: kn10dev.c,v $ + * Revision 2.4 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* +** This file contains generic device support code, including a "null device" +** definition that is used to help initialize other devices. +*/ + +#include /* For stderr */ +#include +#include +#include /* For malloc/free */ + +#include "klh10.h" +#include "osdsup.h" +#include "kn10def.h" +#include "kn10dev.h" +#include "kn10ops.h" +#include "prmstr.h" + +#if KLH10_CPU_KS && KLH10_DEV_TM03 +# include "dvtm03.h" /* For setting up FECOM_BOOTP with magtape params */ +#endif + +#ifdef RCSID + RCSID(kn10dev_c,"$Id: kn10dev.c,v 2.4 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Imported functions */ +extern void pi_devupd(void); + +/* NULL device routines, for handling non-existent device. */ + +static void dvnull_v(struct device *d) { } +static int dvnull_i(struct device *d) { return 0; } +static w10_t dvnull_w(struct device *d) + { register w10_t w; op10m_setz(w); return w; } +static int dvnull_ifs(struct device *d, FILE *f, char *s) + { return 0; } +static int dvnull_ifss(struct device *d, FILE *f, char *s, char *s2) + { return 0; } +static int dvnull_if(struct device *d, FILE *f) + { return 1; } +static dvureg_t dvnull_16(struct device *d) + { return 0; } +static dvureg_t dvnull_rd(struct device *d, dvuadr_t a) + { return 0; } +static void dvnull_wr(struct device *d, dvuadr_t a, dvureg_t u) + { } + +static void dvnull_attn(struct device *d, int i) + { } +static int dvnull_iobeg(struct device *d, int i) + { return 0; } +static int dvnull_iobuf(struct device *d, int wc, w10_t **awp) + { return 0; } +static void dvnull_ioend(struct device *d, int i) + { } +static uint32 dvnull_rrg(struct device *d, int r) + { return -1; } +static int dvnull_wrg(struct device *d, int r, dvureg_t v) + { return 0; } +static int dvnull_evreg(struct device *d, + void (*rtn)(struct device *, struct dvevent_s *), + struct dvevent_s *ev) + { return 0; } +static int dvnull_rim(struct device *d, FILE *f, + w10_t w, w10_t *wp, int cnt) + { return 0; } + + +/* Handle non-existent dev-IO instruction dispatch. +** CONO and DATAO do nothing. +** (DATAO memory read reference already made by dispatch code) +** CONI and DATAI both read 0 +** (Memory write ref will be made by dispatch code) +*/ +static insdef_cono( dvnull_cono) { } +static insdef_datao(dvnull_datao) { } +static insdef_coni( dvnull_coni) { register w10_t w; op10m_setz(w); return w;} +static insdef_datai(dvnull_datai) { register w10_t w; op10m_setz(w); return w;} + +/* Set up null (non-ex) device vector. +** Until vector stabilizes, easier to init it dynamically than statically. +*/ +void +iodv_setnull(register struct device *d) +{ + d->dv_vers = KN10DEV_VERSION; /* Device interface version */ + d->dv_dflags = 0; /* Device flags */ + d->dv_cflags = 0; /* [C] CPU flags */ + d->dv_name = " NULDEV "; /* [C] Device name (unique) */ + d->dv_debug = 0; /* [C] Bit flags for debugging options */ + d->dv_dbf = stderr; /* [C] Debug output stream */ + d->dv_pireq = 0; /* [C/D] NZ = bit for level PI req active on */ + d->dv_pifun = dvnull_attn; /* [C] PI request function */ + d->dv_evreg = dvnull_evreg; /* [C] Event callback reg function */ + d->dv_dpp = NULL; /* Pointer to DP struct, NULL cuz none */ + + d->dv_ctlr = NULL; /* [C] Back-pointer to parent controller */ + d->dv_num = 0; /* [C] Unit number on parent controller */ + d->dv_attn = dvnull_attn; /* [C] Attention request */ + d->dv_drerr = dvnull_v; /* [C] Drive or xfer error */ + d->dv_iobeg = dvnull_iobeg; /* [C] Xfer start */ + d->dv_iobuf = dvnull_iobuf; /* [C] Xfer buffer setup */ + d->dv_ioend = dvnull_ioend; /* [C] Xfer end */ + d->dv_rdreg = dvnull_rrg; /* Drive register read */ + d->dv_wrreg = dvnull_wrg; /* Drive register write */ + + /* KA/KI/KL ("dev" IO )*/ + + d->dv_pifnwd = dvnull_w; /* PI: Get function word */ + d->dv_cono = dvnull_cono; /* CONO 18-bit conds out */ + d->dv_coni = dvnull_coni; /* CONI 36-bit conds in */ + d->dv_datao = dvnull_datao; /* DATAO word out */ + d->dv_datai = dvnull_datai; /* DATAI word in */ + + /* KS ("new" IO) */ + + d->dv_uba = NULL; /* Unibus adapter */ + d->dv_addr = 0; /* 1st valid Unibus address */ + d->dv_aend = 0; /* Last valid Unibus address */ + d->dv_brlev = 0; /* BR setting */ + d->dv_brvec = 0; /* BR interrupt vector setting */ + d->dv_pivec = dvnull_16; /* PI: Get vector */ + d->dv_read = dvnull_rd; /* Read Unibus register */ + d->dv_write = dvnull_wr; /* Write Unibus register */ + + d->dv_bind = NULL; /* Bind device to controller if non-NULL */ + d->dv_init = dvnull_if; /* Final post-bind device init */ + d->dv_exit = dvnull_if; /* Exit, close, terminate */ + d->dv_cmd = dvnull_ifs; /* User command to device */ + d->dv_mount = dvnull_ifss; /* Mount or unmount media */ + d->dv_help = dvnull_if; /* Output help info for user */ + d->dv_status = dvnull_if; /* Output status info for user */ + + d->dv_readin = dvnull_rim; /* Readin mode */ + d->dv_powon = dvnull_v; /* "Power on" */ + d->dv_reset = dvnull_v; /* System reset */ + d->dv_powoff = dvnull_v; /* "Power off" */ +} + + +/* External routine in case want to know what CPU thinks version is +*/ +int +iodv_version(void) +{ + return KN10DEV_VERSION; +} + +/* IODV_NULLINIT - Initialize as null device, check version # +*/ +int +iodv_nullinit(register struct device *d, + int version) +{ + if (version != KN10DEV_VERSION) + return FALSE; + iodv_setnull(d); /* Sets dv_vers to KN10DEV_VERSION */ + return TRUE; +} + + +/* Set max # of registered devices. +** Table can actually be dynamic, but for now let's keep it simple. +*/ +#ifndef KLH10_DEVMAX +# define KLH10_DEVMAX 20 +#endif + +/* Warning - using arrays for the two tables below is good for +** keyword lookup and general searching. However, it is bad if +** unloading or undefining is allowed, because the array must then +** be compacted and things like dev_drv adjusted to point into the +** right place. Ugh. If dynamic unloading/undefining is desired, +** a linked list is probably best, with a separate keyword table +** for parsing purposes. +** +** Don't free up static entries. +*/ + + +/* Device Driver/Emulator Module - information for both static (link-time) +** and extern (dynamic link, run-time) drivers. +** drv_path is NULL if driver is static. +** drv_init is NULL if driver isn't loaded yet (but shouldn't happen) +*/ +struct dvdrv_s { + char *drv_name; /* Name (used after "static" keyword) */ + struct device *(*drv_init)(FILE *, /* Addr of dev config rtn */ + char *); + char *drv_path; /* Path, if dynamically loadable */ + char *drv_isym; /* Symbol, if " ", for device config rtn */ + char *drv_desc; /* Description for help printout */ + osdll_t drv_dllhdl; /* Handle for library, if loaded */ +}; + + +/* Device Definition - binds 10's idea of devices to a specific driver. +** There is one such entry for every instance of a device. +*/ +struct dvdef_s { + char *dev_name; /* Defined name of device */ + int dev_flags; /* Various */ + struct dvdef_s *dev_ctl; /* Pointer to controller dev, if one */ + int dev_num; /* Device # or unibus # or drive unit # */ + struct device *dev_dv; /* Pointer to active device structure */ + struct dvdrv_s *dev_drv; /* Pointer to selected driver code */ +}; +int dvdefcnt = 0; +struct dvdef_s dvdeftab[KLH10_DEVMAX+1]; + +#define DVDF_EXTERN 01 /* Device is dynamically loaded library */ +#define DVDF_STATIC 02 /* Device is statically linked module */ +#define DVDF_DEVIO 04 /* Device expects ,E I/O instrs */ +#define DVDF_UBIO 010 /* Device expects KS10 Unibus I/O instrs */ +#define DVDF_CTLIO 020 /* Device invoked as unit of a controller */ + +/* Predefined device number mnemonics +** These can be provided as the <10dev> spec in a device definition. +** Combine these with opcdvnam[] later. +*/ +struct dvnum_s { + char *dvn_name; + int dvn_num; +}; +static struct dvnum_s dvnumtab[] = { + + { "apr", IODV_APR<<2 }, /* Processor */ + { "pi", IODV_PI <<2 }, /* PI system */ + { "pag", IODV_PAG<<2 }, /* KI+: Pager */ + { "cca", IODV_CCA<<2 }, /* KL: Cache */ + { "tim", IODV_TIM<<2 }, /* KL: Timers, KS: Read various */ + { "mtr", IODV_MTR<<2 }, /* KL: Meters, KS: Write ditto */ + + /* For fun */ + { "clk", IODV_CLK<<2 }, /* DKN10 clock (KA/KI) */ + { "ptp", IODV_PTP<<2 }, /* Paper-Tape Punch */ + { "ptr", IODV_PTR<<2 }, /* Paper-Tape Reader */ + { "tty", IODV_TTY<<2 }, /* Console TTY */ + { "dis", IODV_DIS<<2 }, /* Ye 340 display */ + { NULL, 0 } +}; + + +/* Without good macros, C forward declarations suck... +*/ +#if KLH10_DEV_DTE +extern struct device *dvdte_create(FILE *f, char *s); +#endif +#if KLH10_DEV_RH20 +extern struct device *dvrh20_create(FILE *f, char *s); +#endif +#if KLH10_DEV_RPXX +extern struct device *dvrp_create(FILE *f, char *s); +#endif +#if KLH10_DEV_TM03 +extern struct device *dvtm03_create(FILE *f, char *s); +#endif +#if KLH10_DEV_NI20 +extern struct device *dvni20_create(FILE *f, char *s); +#endif +#if KLH10_DEV_HOST +extern struct device *dvhost_create(FILE *f, char *s); +#endif +#if KLH10_DEV_RH11 +extern struct device *dvrh11_create(FILE *f, char *s); +#endif +#if KLH10_DEV_LHDH +extern struct device *dvlhdh_create(FILE *f, char *s); +#endif +#if KLH10_DEV_DZ11 +extern struct device *dvdz11_init(FILE *f, char *s); +#endif +#if KLH10_DEV_CH11 +extern struct device *dvch11_init(FILE *f, char *s); +#endif + +/* Table binding static device driver modules with their names +*/ +static struct dvdrv_s dvdrvtab[KLH10_DEVMAX+1] = { +#if KLH10_DEV_DTE + { "dte", dvdte_create, NULL, NULL, "DTE 10-11 Interface (dev)" }, +#endif +#if KLH10_DEV_RH20 + { "rh20", dvrh20_create, NULL, NULL, "RH20 Massbus Controller (MB dev)" }, +#endif +#if KLH10_DEV_RPXX + { "rp", dvrp_create, NULL, NULL, "RP/RM Disk Drive (unit)" }, +#endif +#if KLH10_DEV_TM03 + /* Keep "tm02" as a backward-compatible alias for "tm03" */ + { "tm02", dvtm03_create, NULL, NULL, "TM03 Tape Drive (unit)" }, + { "tm03", dvtm03_create, NULL, NULL, "TM03 Tape Drive (unit)" }, +#endif +#if KLH10_DEV_NI20 + { "ni20", dvni20_create, NULL, NULL, "NIA20 Ethernet Port (MB dev)" }, +#endif +#if KLH10_DEV_HOST + { "host", dvhost_create, NULL, NULL, "HOST native access (dev or Unibus)" }, +#endif +#if KLH10_DEV_RH11 + { "rh11", dvrh11_create, NULL, NULL, "RH11 Controller (Unibus)" }, +#endif +#if KLH10_DEV_LHDH + { "lhdh", dvlhdh_create, NULL, NULL, "LHDH IMP Interface (Unibus)" }, +#endif +#if KLH10_DEV_DZ11 + { "dz11", dvdz11_init, NULL, NULL, "DZ11 dummy (Unibus)" }, +#endif +#if KLH10_DEV_CH11 + { "ch11", dvch11_init, NULL, NULL, "Chaosnet dummy (Unibus)" }, +#endif + { NULL, NULL, NULL } +}; + +#if !KLH10_CPU_KS + +/* General Dev-IO support - "IOB" for I/O Bus. +** Eventually move this page elsewhere, either into dviob.c or +** perhaps inio.c? +*/ + +struct device dvnull; /* Null device - default vector */ + +/* Actual binding of dev-IO devices! +*/ +struct device *devtab[128]; + +/* Sub-binding tables. On the KL, all devices ultimately belong to one +** of the following three groups: RH20, DTE, or DIA. +*/ +#if KLH10_CPU_KL +# define NRH20 8 +# define DEVRH20(n) (0540+((n)<<2)) /* Cvt 0-7 into RH20 devs 540-574 */ +struct device *devrh20[8+1]; /* Up to 8 RH20 channels, in order of binding + ** (NOT indexed by device number!) + ** Plus one terminating null pointer. + ** Code assumes all entries null initially! + */ +# define NDTE20 4 +# define DEVDTE20(n) (0200+((n)<<2)) +struct device *devdte20[4+1]; /* 4 DTE20s, devs 200-214 */ + /* Same format as devrh20 table */ + +/* Not clear if the DIA20 is a distinct device or merely a transparent +** hookup to a traditional IO bus to support all other random device #s. +*/ +struct device *devdia20; /* One DIA20 for random devs */ +#endif /* KL */ + + +static void +dviob_init(void) +{ + iodv_setnull(&dvnull); /* Initialize null device vector */ + { + register int i; + for (i = 0; i < 128; i++) + devtab[i] = &dvnull; + } +} + +#if KLH10_CPU_KL + +static void +rh20pifun(register struct device *d, int lev) +{ + if (lev) { /* Requesting PI on level? */ + d->dv_pireq = lev; /* Force new level for device */ + if (cpu.pi.pilev_rhreq & lev) /* Already being requested? */ + return; /* Yes, don't yell at CPU again */ + cpu.pi.pilev_rhreq |= lev; + } else { + if (!(lev = d->dv_pireq)) /* Turning off -- dev already off? */ + return; /* Yep, nothing to do */ + d->dv_pireq = 0; /* Force level off for device */ + if (!(cpu.pi.pilev_rhreq & lev)) /* RHs already turned off? */ + return; /* Yes, don't yell at CPU again */ + + /* Ugh, generic RH PI indicator is turned on. Need to re-assess all + ** RHs to see if safe to turn off bit. Do this by simply summing + ** all RH request bits. + ** In a multiproc environment this scan is a critical section. + */ + { + register struct device **adv; + + cpu.pi.pilev_rhreq = 0; + for (adv = devrh20; *adv; ++adv) + cpu.pi.pilev_rhreq |= (*adv)->dv_pireq; + } + } + pi_devupd(); /* Update main PI state */ +} + +static void +dte20pifun(register struct device *d, int lev) +{ + if (lev) { /* Requesting PI on level? */ + d->dv_pireq = lev; /* Force new level for device */ + if (cpu.pi.pilev_dtereq & lev) /* Already being requested? */ + return; /* Yes, don't yell at CPU again */ + cpu.pi.pilev_dtereq |= lev; + } else { + if (!(lev = d->dv_pireq)) /* Turning off -- dev already off? */ + return; /* Yep, nothing to do */ + d->dv_pireq = 0; /* Force level off for device */ + if (!(cpu.pi.pilev_dtereq & lev)) /* DTEs already turned off? */ + return; /* Yes, don't yell at CPU again */ + + /* Ugh, generic DTE PI indicator is turned on. Need to re-assess all + ** DTEs to see if safe to turn off bit. Do this by simply summing + ** all DTE request bits. + ** In a multiproc environment this scan is a critical section. + */ + { + register struct device **adv; + + cpu.pi.pilev_dtereq = 0; + for (adv = devdte20; *adv; ++adv) + cpu.pi.pilev_dtereq |= (*adv)->dv_pireq; + } + } + pi_devupd(); /* Update main PI state */ +} + +#endif /* KL */ + + +static int +dviob_add(FILE *of, /* Error output */ + register struct device *d, /* Device (post-define, pre-init) */ + int num) /* IO Bus Device # to bind it to */ +{ + register int i; + + if (devtab[num>>2] != &dvnull) { + if (of) + fprintf(of, "Cannot bind to IO device %o: already in use\n", num); + return FALSE; + } + +#if KLH10_CPU_KL + /* Do special hackery on KL for Massbus and DTE devs */ + if (DEVRH20(0) <= num && num < DEVRH20(8)) { + for (i = 0; i < NRH20; ++i) { + if (!devrh20[i] || (devrh20[i] == d)) + break; /* Found entry, stop loop */ + } + if (i >= NRH20) { + if (of) + fprintf(of, "Cannot bind RH20 %o: No slots left?!\n", num); + return FALSE; + } + d->dv_pifun = rh20pifun; + devrh20[i] = d; + } else if (DEVDTE20(0) <= num && num < DEVDTE20(4)) { + for (i = 0; i < NDTE20; ++i) { + if (!devdte20[i] || (devdte20[i] == d)) + break; /* Found entry, stop loop */ + } + if (i >= NDTE20) { + if (of) + fprintf(of, "Cannot bind DTE20 %o: No slots left?!\n", num); + return FALSE; + } + d->dv_pifun = dte20pifun; + devdte20[i] = d; + } +#endif /* KL */ + + devtab[num>>2] = d; /* Bind it! */ + d->dv_num = num; /* Remember device # bound to */ + + return TRUE; +} + +#endif /* !KS */ + +/* DEV_INIT +** Initializes all device support. Called from main startup code. +*/ +void +dev_init(void) +{ + dvdefcnt = 0; + memset((char *)dvdeftab, 0, sizeof(dvdeftab)); + +#if KLH10_EVHS_INT + dev_evinit(); +#endif + +#if KLH10_CPU_KS + dvub_init(); /* New-style Unibus devices */ +#else + dviob_init(); /* Old-style I/O bus devices */ +#endif +} + +/* DEV_TERM - Terminate device support. +** Called from main shutdown code. +** +** Doesn't bother cleaning up data structures; merely powers off all +** devices in case they're using system resources that need to be +** reclaimed. +*/ +void +dev_term(void) +{ + register struct dvdef_s *df; + + /* Make two passes through the device definition table; once + ** to turn off all controller subdevices, and then to turn off + ** all others (controllers and otherwise). + ** This ensures that the subdevice being powered off can count on + ** its controller still being present. + */ + for (df = dvdeftab; df < &dvdeftab[KLH10_DEVMAX]; ++df) { + if (df->dev_name && (df->dev_flags & DVDF_CTLIO)) { + (*df->dev_dv->dv_powoff)(df->dev_dv); /* Turn it off */ + } + } + + /* Second pass, turn off everything that's NOT a controller subdev */ + for (df = dvdeftab; df < &dvdeftab[KLH10_DEVMAX]; ++df) { + if (df->dev_name && !(df->dev_flags & DVDF_CTLIO)) { + (*df->dev_dv->dv_powoff)(df->dev_dv); /* Turn it off */ + } + } +} + +/* DEV_LOOKUP - Look up device definition +*/ +static struct dvdef_s * +dev_lookup(char *name) +{ + return (struct dvdef_s *) + s_fkeylookup(name, (void *)dvdeftab, sizeof(struct dvdef_s)); +} + +/* DEV_DRVLOOKUP - Look up device driver +*/ +static struct dvdrv_s * +dev_drvlookup(char *name) +{ + return (struct dvdrv_s *) + s_fkeylookup(name, (void *)dvdrvtab, sizeof(struct dvdrv_s)); +} + +/* DEV_NUMLOOKUP - Look up device number +*/ +static int +dev_numlookup(char *name) +{ + struct dvnum_s *dn; + + dn = (struct dvnum_s *) + s_fkeylookup(name, (void *)dvnumtab, sizeof(*dn)); + return (dn ? dn->dvn_num : -1); +} + + +/* Auxiliaries for dev_define. */ + +static struct dvdef_s * +dev_make(void) +{ + register struct dvdef_s *dp; + + if (dvdefcnt >= KLH10_DEVMAX-1) { + /* Print error msg? */ + return NULL; + } + for (dp = dvdeftab; dp < &dvdeftab[KLH10_DEVMAX]; ++dp) + if (! dp->dev_name) { + memset((char *)dp, 0, sizeof(*dp)); /* Ensure entry cleared */ + ++dvdefcnt; + return dp; /* Won */ + } + return NULL; +} + +static void +dev_unmake(struct dvdef_s *dp) +{ + if (dp->dev_name) { + free(dp->dev_name); + dp->dev_name = NULL; + } + --dvdefcnt; +} + + +/* Auxiliaries for dev_drvload. */ + +static struct dvdrv_s * +dev_drvmake(void) +{ + register struct dvdrv_s *dp; + + /* Scan entire table to find first free entry */ + for (dp = dvdrvtab; dp < &dvdrvtab[KLH10_DEVMAX]; ++dp) + if (! dp->drv_name) { + memset((char *)dp, 0, sizeof(*dp)); /* Ensure entry cleared */ + return dp; /* Won */ + } + return NULL; +} + +static void +dev_drvunmake(struct dvdrv_s *dp) +{ + if (dp->drv_path || dp->drv_isym) { + if (dp->drv_name) + free(dp->drv_name); + if (dp->drv_path) + free(dp->drv_path); + if (dp->drv_isym) + free(dp->drv_isym); + if (dp->drv_desc) + free(dp->drv_desc); + memset((char *)dp, 0, sizeof(*dp)); + } +} + +/* DEV_LOAD - Load a dynamic/shared library as a device driver, +** and defines its name for later use in configuration. +** - Arbitrary device driver name. +** - Pathname for library in native OS. +** - "Entry point" symbol (init/config routine) +*/ + +int +dev_drvload(FILE *of, char *name, char *path, char *isym, char *args) +{ + struct dvdrv_s *dp, ddef; + + if (!(dp = dev_drvmake())) { + if (of) + fprintf(of, "Too many drivers, cannot load another\n"); + return FALSE; + } + + /* Check out 1st arg (driver name) for pre-existence */ + if (dev_drvlookup(name)) { + if (of) + fprintf(of, "Device \"%s\" already exists, cannot reload\n", name); + dev_drvunmake(dp); + return FALSE; + } + + /* Set up rest of entry. Do this first because that's easier to back + ** out of, if load fails, than the reverse (i.e. unloading the driver!) + */ + if ( !(dp->drv_name = s_dup(name)) + || !(dp->drv_path = s_dup(path)) + || !(dp->drv_isym = s_dup(isym)) + || !(dp->drv_desc = s_dup(args))) { /* Rest of line is comment */ + if (of) + fprintf(of, "Cannot malloc space for dvdrv entry\n"); + dev_drvunmake(dp); + return FALSE; + } + + /* Attempt to load up library. This is very OS dependent. + ** For now, bundle the symbol lookup in there too because OS may not + ** be able to do it separately. + */ + if (!os_dlload(of, ddef.drv_path, &(dp->drv_dllhdl), + ddef.drv_isym, (void **)&(dp->drv_init))) { + dev_drvunmake(dp); /* If fail, error already reported */ + return FALSE; + } + + return TRUE; /* Yow! Yow!*/ +} + + +/* DEV_DEFINE - Do initial device configuration +** Defines the device name and does its initial configuration setup. +** +** - Name to identify device +** - A PDP-10 device number (dev-IO), +** or predefined keyword (dev-IO) such as CTY, +** or UB - A KS10 Unibus Adapter (n = 1 or 3) +** - Name of a driver module, either builtin or dynamic. +** - Optional args to dev's definition routine (rest of line) +** +** If the and args check out, the following steps are done: +** (1) The driver module's "define" routine is called to obtain a +** device struct for it. +** (2) The device is bound either to a controller or directly to the 10. +** This is done by invoking the appropriate bind function. +** For the 10 this depends on the bus involved. +** For controllers, this is the dv_bind vector. +** (3) If bind succeeds, the device init is completed by invoking its +** dv_init vector. +*/ +int +dev_define(FILE *of, char *name, char *sdev, char *sdrv, char *args) +{ + char *cp; + register int dnum, flags = 0; + long lnum; + register struct dvdef_s *dp; + struct dvdrv_s *drv; + struct dvdef_s *ctldef = NULL; /* Possible controller */ + + /* Perhaps check here to make sure there's room for another device def + ** before going farther, but no big deal. + */ + + /* Check out device name for pre-existence */ + if (dev_lookup(name)) { + if (of) + fprintf(of, "Device \"%s\" already exists, cannot redefine\n", + name); + return FALSE; + } + + /* Parse 10-device binding specification. + ** Syntax is a bit peculiar, depends on # of components. + ** If 2, 1st must be an existing device name and the 2nd a plain number. + */ + if (cp = strchr(sdev, '.')) { + char dnbuf[100]; + int i = cp - sdev; + + if (i >= (sizeof(dnbuf)-1)) { + if (of) + fprintf(of, "Device binding \"%s\" too long.\n", sdev); + return FALSE; + } + memcpy(dnbuf, sdev, i); + dnbuf[i] = '\0'; + if (!(ctldef = dev_lookup(dnbuf))) { + if (of) + fprintf(of, "Unknown device \"%s\" in binding \"%s\".\n", + dnbuf, sdev); + return FALSE; + } + if (!ctldef->dev_dv->dv_bind) { + if (of) + fprintf(of, "Device \"%s\" cannot accept bindings.\n", dnbuf); + return FALSE; + } + /* Have valid controller spec! Now get unit # for it... */ + if (!s_tonum(++cp, &lnum)) { /* Parse rest as unit number */ + if (of) + fprintf(of, "Unit binding must be a number (\"%s\" invalid).", + cp); + return FALSE; + } + dnum = (int)lnum; + flags |= DVDF_CTLIO; + + } else { + cp = sdev; + if (isdigit(*cp)) { + if (s_tonum(cp, &lnum)) /* Parse as device number */ + flags |= DVDF_DEVIO; + dnum = (int)lnum; + } else if (*cp == 'u' && cp[1] == 'b') { + if (s_tonum(cp+2, &lnum)) /* Parse as unibus controller # */ + flags |= DVDF_UBIO; + dnum = (int)lnum; + } else { /* Parse as known-10-device name */ + if (-1 != (dnum = dev_numlookup(cp))) /* -1 is error */ + flags |= DVDF_DEVIO; + } + if (!(flags & (DVDF_DEVIO|DVDF_UBIO))) { + if (of) + fprintf(of, "Device num spec \"%s\" not recognized.\n", cp); + return FALSE; + } + } + + /* Do a few sanity checks on the resulting number */ + if (flags & DVDF_UBIO) { + /* Check out for new-style KS10 unibus IO */ +#if KLH10_CPU_KS + if (dnum != 1 && dnum != 3) { + if (of) + fprintf(of, "Bad unibus #: %#.0o - only 1 or 3 supported\n", + dnum); + return FALSE; + } +#else + if (of) + fprintf(of, "Unibus devices not supported on non-KS10\n"); + return FALSE; +#endif + } else if (flags & DVDF_DEVIO) { + /* Check out for old-style device IO */ +#if !KLH10_CPU_KS + if ((dnum & 03) || (dnum > 0774)) { + if (of) + fprintf(of, "Bad dev number %#.0o, must be in {0,4,10,...,774}\n", + dnum); + return FALSE; + } + if (dnum <= (IODV_MTR<<2)) { + if (of) + fprintf(of, "Device %#.0o is internal, cannot be redefined\n", + dnum); + return FALSE; + } +#else + if (of) + fprintf(of, "Old-style IO devices not supported on KS10\n"); + return FALSE; +#endif /* KS */ + } + + /* Parse driver keyword */ + if (!(drv = dev_drvlookup(sdrv))) { + if (of) + fprintf(of, "Unknown device driver \"%s\"\n", sdrv); + return FALSE; + } + + /* Have everything set up! Now put together real device definition. */ + if (!(dp = dev_make())) { + if (of) + fprintf(of, "Out of room for more device defs\n"); + return FALSE; + } + if (!(dp->dev_name = s_dup(name))) { /* Finally make it valid */ + if (of) + fprintf(of, "Cannot malloc copy of device name\n"); + dev_unmake(dp); + return FALSE; + } + dp->dev_ctl = ctldef; + dp->dev_num = dnum; + dp->dev_drv = drv; + dp->dev_flags = flags; + + /* Invoke the driver's init-config routine on the rest of the arg line! */ + if ((dp->dev_dv = (*(drv->drv_init))(of, args)) == NULL) { + if (of) + fprintf(of, "Device init failed\n"); + /* Maybe need to flush device? */ + dev_unmake(dp); /* Flush entry, free stg */ + return FALSE; + } + dp->dev_dv->dv_name = dp->dev_name; /* Won, set name prior to bind */ +#if KLH10_EVHS_INT + dp->dev_dv->dv_evreg = dev_evreg; /* Allow dev to register for events */ +#endif + + /* Now bind device to 10-side */ +#if KLH10_CPU_KS + if (dp->dev_flags & DVDF_UBIO) { + /* Bind to specified unibus. This call reports its own errors. + */ + dp->dev_dv->dv_dflags |= DVFL_UBIO; + if (!dvub_add(of, dp->dev_dv, dp->dev_num)) { + dev_unmake(dp); + return FALSE; + } + } +#endif +#if !KLH10_CPU_KS + if (dp->dev_flags & DVDF_DEVIO) { + /* Bind to specified PDP-10 device #. Reports own errors. + */ + dp->dev_dv->dv_dflags |= DVFL_DEVIO; + if (!dviob_add(of, dp->dev_dv, dp->dev_num)) { + dev_unmake(dp); + return FALSE; + } + } +#endif /* !KS */ + + /* Ensure device agrees as to whether it's a controlled unit */ + if (((dp->dev_flags & DVDF_CTLIO)==0) + != ((dp->dev_dv->dv_dflags & DVFL_CTLIO)==0)) { + if (of) + fprintf(of, + "Ctlr/unit mismatch? Device %s %s to be controlled.\n", + dp->dev_name, + (dp->dev_dv->dv_dflags & DVFL_CTLIO) + ? "wants" : "refuses"); + dev_unmake(dp); + return FALSE; + } + if (dp->dev_flags & DVDF_CTLIO) { + /* Bind to specified controller. Have already checked for + ** existence of dv_bind vector. + */ + dp->dev_dv->dv_ctlr = ctldef->dev_dv; /* Set up backpointer */ + dp->dev_dv->dv_num = dp->dev_num; /* Set up dev unit # */ + if (0 == (*ctldef->dev_dv->dv_bind)(ctldef->dev_dv, of, + dp->dev_dv, dp->dev_num)) { + if (of) + fprintf(of, "Cannot bind to %s unit %o: already in use?\n", + ctldef->dev_name, dp->dev_num); + dev_unmake(dp); + return FALSE; + } + } + + + /* Binding succeeded, now do final init for device */ + if (0 == (*dp->dev_dv->dv_init)(dp->dev_dv, of)) { + if (of) + fprintf(of, "Final init of device \"%s\" failed!\n", dp->dev_name); + dev_unmake(dp); + return FALSE; + } + + return TRUE; +} + +/* Various device command functions +*/ + +/* Handy auxiliary for most of the commands +*/ +static struct device * +devverify(register FILE *of, + register char *dstr) +{ + struct dvdef_s *def; + + if (!dstr) { + if (of) + fprintf(of, "No device specified\n"); + return NULL; + } + if (!(def = dev_lookup(dstr))) { + if (of) + fprintf(of, "Unknown device \"%s\"\n", dstr); + return NULL; + } + + /* Exercise a little paranoia for now */ + if (!def->dev_dv) { + if (of) + fprintf(of, "Internal error! Null vector for dev_dv !!\n"); + return NULL; + } + return def->dev_dv; +} + +/* Similar auxiliary for anything that wants to hack DPs +** Parse "" - just like devverify, existence of DP is irrelevant. +** *adp is set to NULL. +** also parse ".dp" - Sets *adp to DP pointer; returns NULL if *adp is +** NULL (meaning no DP exists, altho user is trying to +** refer to it). +*/ +static struct device * +devdpverify(register FILE *of, + register char *dstr, + struct dp_s **adp) +{ + register int i; + register char *cp; + char tmpstr[100]; + register struct device *d; + + *adp = NULL; + + /* See if attempting reference to DP of a device */ + if (dstr && (cp = strrchr(dstr, '.')) + && (s_match(cp, ".dp") == 2) + && ((i = cp-dstr) < sizeof(tmpstr))) { + + /* Yes, identify device string. Copy so needn't mung arg */ + memcpy(tmpstr, dstr, i); + tmpstr[i] = '\0'; + if (!(d = devverify(of, tmpstr))) + return NULL; + if (!(*adp = d->dv_dpp)) { + if (of) + fprintf(of, "Device \"%s\" has no DP\n", d->dv_name); + return NULL; + } + return d; /* DP won! */ + } else + return devverify(of, dstr); /* No ".dp" spec, do normal stuff */ +} + + +/* DEV_COMMAND - Generic device command +** Syntax: dev +** +** Invokes device's command parsing and execution, which can be anything +** the device wants to do with the line. Most should follow certain +** conventions, however: +** dev help - Show what this device understands +** dev status - Show status in appropriate form +** dev show [param] [...] - Show specific device vars +** dev set [param=val] [...] - Set them +*/ +int +dev_command(FILE *of, + char *dstr, + char *args) +{ + register struct device *d; + + if (!(d = devverify(of, dstr))) + return FALSE; + if (!d->dv_cmd) { /* Exercise more paranoia */ + if (of) + fprintf(of, "Internal error! Null vector for dv_cmd\n"); + return FALSE; + } + return (*(d->dv_cmd))(d, of, args); /* Invoke it! */ +} + + +int +dev_help(FILE *of, + char *dstr, + char *args) +{ + register struct device *d; + + if (!dstr || strcmp(dstr, "?")==0) { + /* Show general help */ + + return TRUE; + } + if (!(d = devverify(of, dstr))) + return FALSE; + if (!d->dv_help) { /* Exercise more paranoia */ + if (of) + fprintf(of, "Internal error! Null vector for dv_help !!\n"); + return FALSE; + } + + return (*(d->dv_help))(d, of); /* Invoke it! */ +} + +int +dev_status(FILE *of, + char *dstr, + char *args) +{ + register struct device *d; + + if (!dstr || strcmp(dstr, "?")==0) { + /* Show general status */ + return TRUE; + } + if (!(d = devverify(of, dstr))) + return FALSE; + if (!d->dv_status) { /* Exercise more paranoia */ + if (of) + fprintf(of, "Internal error! Null vector for dv_status !!\n"); + return FALSE; + } + + return (*(d->dv_status))(d, of); /* Invoke it! */ +} + +int +dev_mount(FILE *of, + char *dstr, + char *path, + char *args) +{ + register struct device *d; + + if (!(d = devverify(of, dstr))) + return FALSE; + if (!d->dv_mount) { /* Exercise more paranoia */ + if (of) + fprintf(of, "Internal error! Null vector for dv_mount !!\n"); + return FALSE; + } + + return (*(d->dv_mount))(d, of, path, args); /* Invoke it! */ +} + + +int +dev_debug(FILE *of, + char *dstr, + char *sval, + char *args) +{ + long dbgval; + register struct device *d; + struct dp_s *dp; + + if (!dstr || !*dstr || (*dstr == '?')) { + /* Show debug values for all known devices */ + struct dvdef_s *def; + + fprintf(of, " Device debug values:\n"); + for (def = dvdeftab; def->dev_name; ++def) { +#if KLH10_DEV_DP + fprintf(of, " %6s = %d\n", def->dev_name, + def->dev_dv->dv_debug); + if (dp = def->dev_dv->dv_dpp) { + fprintf(of, " %6s.dp = ", def->dev_name); + if (dp->dp_adr) + fprintf(of, "%d\n", dp->dp_adr->dpc_debug); + else + fprintf(of, "??\n"); + } +#else + fprintf(of, " %6s = %d\n", def->dev_name, def->dev_dv->dv_debug); +#endif + } + return TRUE; + } + + if (!(d = devdpverify(of, dstr, &dp))) + return FALSE; + + /* Parse debug value */ + if (!sval || !*sval) { + if (of) { + fprintf(of, " Debug value for %s = ", dstr); +#if KLH10_DEV_DP + if (dp) { + if (dp->dp_adr) + fprintf(of, "%d\n", dp->dp_adr->dpc_debug); + else + fprintf(of, "??\n"); + } else +#endif + fprintf(of, "%d\n", d->dv_debug); + + } + return FALSE; + } + + if (!s_tonum(sval, &dbgval)) { + if (of) + fprintf(of, "Bad syntax - \"%s\" not a number\n", sval); + return FALSE; + } + + /* Won, set it!! */ +#if KLH10_DEV_DP + if (dp) { + if (dp->dp_adr) { + dp->dp_adr->dpc_debug = dbgval; + return TRUE; + } + if (of) + fprintf(of, "DP not active, cannot set debug value\n"); + return FALSE; + } +#endif + d->dv_debug = dbgval; + return TRUE; +} + + + +int +dev_show(FILE *of, + char *dstr, + char *args) +{ + struct dvdef_s *def; + struct dvdrv_s *drv; + + if (!dstr || strcmp(dstr, "?")==0) { + /* Show general config status, etc */ + if (!of) return TRUE; + + /* Show all defined drivers */ + fprintf(of, "Known device drivers:\n"); + fprintf(of, " Name Linkage EntryAddr \"Description\"\n"); + for (drv = dvdrvtab; drv->drv_name; ++drv) { + fprintf(of, " %6s (%s) 0x%lX ", drv->drv_name, + (drv->drv_path ? "extern-dll" : "static-lib"), + (long)(drv->drv_init)); + if (drv->drv_path) + fprintf(of, "{path=%s isym=%s}", drv->drv_path, drv->drv_isym); + fprintf(of, " \"%s\"\n", drv->drv_desc); + } + + /* Show all bound devices */ + fprintf(of, "\nDefined devices:\n"); + fprintf(of, " DevID Dev# Driver StructAddr\n"); + for (def = dvdeftab; def->dev_name; ++def) { + fprintf(of, " %6s ", def->dev_name); + if (def->dev_flags & DVDF_UBIO) { + struct device *dv = def->dev_dv; + fprintf(of, + " UB%d %-6s 0x%lX addr=%#.0o:%#.0o br=%d vec=%#.0o\n", + def->dev_num, def->dev_drv->drv_name, + (unsigned long)dv, + dv->dv_addr, dv->dv_aend, dv->dv_brlev, + dv->dv_brvec); + } else if (def->dev_flags & DVDF_DEVIO) { + fprintf(of, + "%6o %-6s 0x%lX\n", + def->dev_num, def->dev_drv->drv_name, + (unsigned long)def->dev_dv); + } else if (def->dev_flags & DVDF_CTLIO) { + fprintf(of, + "%6s.%o %-6s 0x%lX\n", + def->dev_ctl->dev_name, + def->dev_num, def->dev_drv->drv_name, + (unsigned long)def->dev_dv); + } else + fprintf(of, "\?\?\?\n"); + } + + /* List known device number mnemonics? */ + + return TRUE; + } + + return FALSE; +#if 0 + if (!(def = dev_lookup(dstr))) { + if (of) + fprintf(of, "Unknown device driver \"%s\"\n", dstr); + return FALSE; + } + + /* Exercise a little paranoia for now */ + if (!def->dev_dv || !def->dev_dv->dv_help) { + if (of) + fprintf(of, "Internal error! Null vector for %s!!\n", + def->dev_dv ? "dev_dv" : "dev_dv->dv_help"); + return FALSE; + } + + /* Found it, now invoke it */ + return (*(def->dev_dv->dv_help))(def->dev_dv, of); +#endif +} + +/* Device boot code */ + +/* + +The device boot code doesn't go in any really obvious place as it can +combine CPU instructions, device invocations, and FE knowledge of +where bootstraps are located, the latter being the most devious. + +For now, put it here. + +*/ + +#define FE_BOOTLOC 01000 +#define FE_BOOTLEN 512 + +/* DEV_BOOT - Read bootstrap from device using "read-in mode" +** +*/ +int +dev_boot(FILE *of, + char *dstr, + vaddr_t *bootsa) +{ + register struct device *d, *ctlr; + register w10_t w; + register vmptr_t vp; + int res; + + /* XXX: Later use first controller if no dev specified? */ + + if (!(d = devverify(of, dstr))) + return FALSE; + + /* Determine device type. + XXX: If it's a controller, use first live device on it. + */ + if (d->dv_dflags & DVFL_CTLR) { + if (of) fprintf(of, "Cannot boot from a controller\n"); + return FALSE; + } else + ctlr = d->dv_ctlr; + + /* Have device, now determine whether dealing with random-access + block mode device, or tape device. + */ +#if KLH10_CPU_KS + /* Must set up device variables in FECOM area so boot code knows + where it came from. + */ + if (ctlr) { + W10_XSET(w, ctlr->dv_uba->ubn, ctlr->dv_addr); + vm_pset(vm_physmap(FECOM_RHBAS), w); /* RH11 base address */ + W10_U32SET(w, d->dv_num); + vm_pset(vm_physmap(FECOM_QNUM), w); /* RH11 unit number */ + W10_U32SET(w, (TM_D16<<8) | (TM_FCD<<4) | 0); + vm_pset(vm_physmap(FECOM_BOOTP), w); /* Tape OFTC (dens & slave) */ + } + + W10_U32SET(w, 1); /* Address: one file or one sector in */ + vp = vm_physmap(FE_BOOTLOC); /* Read into here */ + if (d->dv_dflags & DVFL_TAPE) { + /* A boot tape is set up so the first file is microcode, and + the second is a PDP-10 boot image. The first record of that + boot (normally 512 wds) is read into loc 1000 and the 10 + started there. + */ + if (of) fprintf(of, "Reading 1st record of 2nd file ... "); + res = (*d->dv_readin)(d, of, w, (w10_t *)vp, FE_BOOTLEN); + if (!res) + return FALSE; + if (of) fprintf(of, "OK\nRead in %ld words at %lo\n", + (long)res, (long)FE_BOOTLOC); + *bootsa = FE_BOOTLOC; + + } else if (d->dv_dflags & DVFL_DISK) { + /* KS disk boot is messy. + First reads sector #1 (each sector 128 words) + and verifies first word is 'HOM '. + Then reads sector #010 and does same verification. + If OK, takes disk address from loc +103 in that block, + reads it, gets another disk address from loc +4, + and finally uses that to read the first 512 words (4 + sectors) of the "real" 10 boot. Jumps to 1000 to start. + */ + w10_t w6hom = W10_XINIT(0505755,0); /* sixbit /HOM / */ + + if (of) fprintf(of, "Reading HOM sector 1 ... "); + if (!(res = (*d->dv_readin)(d, of, w, (w10_t *)vp, 128))) + return FALSE; + if (op10m_camn(vm_pget(vp), w6hom)) { + if (of) fprintf(of, "Bad HOM check, val = %lo,,%lo\n", + 1, (long)LHGET(w), (long)RHGET(w)); + return FALSE; + } + if (of) fprintf(of, "OK\nReading HOM sector 010 ..."); + W10_U32SET(w, 010); + if (!(res = (*d->dv_readin)(d, of, w, (w10_t *)vp, 128))) + return FALSE; + if (op10m_camn(vm_pget(vp), w6hom)) { + if (of) fprintf(of, "Bad HOM check, val = %lo,,%lo\n", + 010, (long)LHGET(w), (long)RHGET(w)); + return FALSE; + } + w = vm_pget(vm_physmap(FE_BOOTLOC+0103)); /* Get ptrs pag addr */ + if (op10m_skipn(w)) { + if (of) fprintf(of, "Bad ptr pag addr from HOM blk = %lo,,%lo\n", + (long)LHGET(w), (long)RHGET(w)); + return FALSE; + } + if (of) fprintf(of, "OK\nReading pointers sector at %lo,,%lo ...", + (long)LHGET(w), (long)RHGET(w)); + if (!(res = (*d->dv_readin)(d, of, w, (w10_t *)vp, 128))) + return FALSE; + w = vm_pget(vm_physmap(FE_BOOTLOC+04)); /* Get boot code addr */ + if (op10m_skipn(w)) { + if (of) fprintf(of, "Bad bootcode addr from ptr blk = %lo,,%lo\n", + (long)LHGET(w), (long)RHGET(w)); + return FALSE; + } + + /* Finally! Note we now read in a full page (4 sectors) */ + if (of) fprintf(of, "OK\nReading 4 boot sectors at %lo,,%lo ...", + (long)LHGET(w), (long)RHGET(w)); + if (!(res = (*d->dv_readin)(d, of, w, (w10_t *)vp, 512))) + return FALSE; + + if (of) fprintf(of, "OK\nRead in %ld words at %lo\n", + (long)res, (long)FE_BOOTLOC); + *bootsa = FE_BOOTLOC; + + } + +#elif KLH10_CPU_KA || KLH10_CPU_KI + /* Do one DATAI into loc 0, then treat it as + a BLKI pointer. When done, KI jumps to last word, + KA executes it. + */ + { + int32 cnt; +#if KLH10_CPU_KA + w10_t tmp = W10XWD(1,1); /* KA adds 1,,1 */ +#endif + w = (*d->dv_datai)(d); + + if ((cnt = LHGET(w)) & H10SIGN) + cnt = -(cnt | ~((int32)MASK18)); + vp = vm_xrwmap(0); /* Will be in AC so map it */ + + if (of) fprintf(of, "Read BLKI=%lo,,%lo (%ld words)\n" + LHGET(w), RHGET(w), (long)cnt); + vm_pset(vp, w); + + /* Now do BLKI loop. Always inputs at least one word */ + for (cnt = 1; ; ++cnt) { + /* Get word and store it. + Re-map each time since may cross AC or address bounds + */ + vm_pset(vm_xrwmap(RHGET(w)), (*d->dv_datai)(d)); +#if KLH10_CPU_KA + op10m_add(w, tmp); /* KA adds 1,,1 */ +#else /* KI increments independently */ + LRHSET(w, (LHGET(w)+1)&H10MASK, (RHGET(w)+1)&H10MASK); +#endif + vm_pset(vp, w); /* Store back inc'd pointer */ + if (LHGET(w) == 0) + break; + } + if (of) fprintf(of, "Readin complete, %ld words\n", (long)cnt); + *bootsa = (vaddr_t)RHGET(w); + } + +#else + if (0) ; +#endif + else { + if (of) fprintf(of, "%s not supported for boot\n", d->dv_name); + return FALSE; + } + return TRUE; +} + + +/* First stab at support for device subprocs -- cooperates with DPSUP code */ + +#if KLH10_EVHS_INT + +/* Event callback registration: + + The mechanism for organizing asynchronous events will rely on +having handlers register themselves with DEV_EVREG. The various types +of events understood are defined in kn10dev.h as DVEV_xxx values. + For now, event handlers are *only* invoked atomically between +instructions, as part of INSBRK processing. They are never invoked +at "OS interrupt level", i.e. as part of an OS signal handler. Such +signal handlers merely set the appropriate flags so that INSBRK will +know that the respective event happened. + If handlers generate any PI interrupts, those will take effect +before any instruction is executed. + +Some notes on specific events: + + DVEV_1SIG - Single signal specified by eva_int. + Event handler wants to monopolize this signal -- registration fails + if anyone else tries to register for it. + When signal happens, its handler is always invoked. + + DVEV_NSIG - Multiplexed signal specified by eva_int. + Event handler is willing to share this signal. + When signal happens, *all* registered handlers are invoked, since + without additional hackery there's no way to know which one was + intended to receive the signal. + This is rather inefficient and at the top of the list. + + DVEV_ASIG - Allocated signal. No arg; signal value is returned. + This type asks the registrar to find an unused single signal + if possible, or a multiplexed one if not. + + DVEV_DPSIG - Device subProcess signal specified by eva_int. + Pointer to flag check word specified by 2nd arg's eva_ip. + Like DVEV_NSIG but handler only invoked if flag is non-zero + (flag is cleared just prior to invoking handler). + + DVEV_CLOCK - Periodic clock callout. Time in eva_int is # msec. + The time will be converted to some # of internal clock ticks + by rounding to the nearest tick (but no less than 1). + + DVEV_TIMOUT - Clock timeout. Time in eva_int is # msec. + Same as DVEV_CLOCK except routine is de-registered just before + the callback is made. + +*/ + + +#ifndef SIGMAX +# ifndef NSIG +# define SIGMAX 32 /* Probable guess */ +# else +# define SIGMAX NSIG +# endif +#endif + +#ifndef KLH10_EVHS_MAX +# define KLH10_EVHS_MAX (KLH10_DEVMAX+10) +#endif + +struct dvevsig_s *evsiglist; +struct dvevsig_s evsigtab[SIGMAX]; /* Registered signals */ + +struct dvevreg_s *evregfree; +struct dvevreg_s evregtab[KLH10_EVHS_MAX]; /* Registered handlers */ + +static void dev_evunreg(struct device *); + +/* DEV_EVINIT - Initialize event stuff +*/ +void +dev_evinit(void) +{ + register struct dvevreg_s *evr; + + INTF_INIT(cpu.intf_evsig); + evsiglist = NULL; + memset((char *)evsigtab, 0, sizeof(evsigtab)); + memset((char *)evregtab, 0, sizeof(evregtab)); + + /* Set up handler entry freelist */ + evregfree = evregtab; + for (evr = evregfree; evr < &evregtab[KLH10_EVHS_MAX-1]; ++evr) + evr->dver_next = evr+1; + evr->dver_next = NULL; +} + +static void +dev_sighan(int sig) +{ + if (0 < sig && sig < SIGMAX) { /* Paranoia */ + INTF_SET(evsigtab[sig].dves_intf); /* Say this signal seen */ + INTF_SET(cpu.intf_evsig); /* Say some signal seen */ + INSBRKSET(); /* Interrupt instr loop */ + } +} + +/* DEV_EVCHECK - Called at INSBRK to process any valid events +*/ + +void +dev_evcheck(void) +{ + register struct dvevsig_s *evs; + register struct dvevreg_s *evr; + + /* Check all signals to see which ones went off */ + for (evs = evsiglist; evs; evs = evs->dves_next) { + if (INTF_TEST(evs->dves_intf)) { + INTF_ACTBEG(evs->dves_intf); + + /* Process all handlers registered for this signal */ + for (evr = evs->dves_reglist; evr; evr = evr->dver_next) { + if (*(evr->dver_ev.dvev_arg2.eva_ip)) { + /* If this handler's flag shows it really does want + ** the call, invoke handler after clearing flag! + */ + *(evr->dver_ev.dvev_arg2.eva_ip) = 0; + (*(evr->dver_hdlr))(evr->dver_d, &(evr->dver_ev)); + } + } + + INTF_ACTEND(evs->dves_intf); + } + } +} + +/* DEV_EVREG - Called by devices through device vector. +** Registers a callback routine for handling a specific event. +** If any suitable event happens, routine will be invoked atomically +** between instructions (as part of INSBRK processing). It should +** be defined as: +** callback_rtn(struct device *d, int evtyp, int evarg) +** +*/ +int +dev_evreg(register struct device *d, + void (*rtn)(struct device *, struct dvevent_s *), + register struct dvevent_s *evp) +{ + register struct dvevreg_s *evr; + register struct dvevsig_s *evs; + int sig; + ossigset_t oldmask, allmask; + + /* First ensure we aren't interrupted while messing with table */ + os_sigfillset(&allmask); + (void) os_sigsetmask(&allmask, &oldmask); + + /* Check if unregistering device and handle if so */ + if (rtn == NULL) { + dev_evunreg(d); /* Flush it */ + (void) os_sigsetmask(&oldmask, (ossigset_t *)NULL); + return TRUE; + } + + /* Find new entry in evreg table */ + if (evr = evregfree) { /* Pluck from freelist */ + evregfree = evr->dver_next; /* Freelist is one-way, no prev */ + } else { + fprintf(stderr, "[dev_evreg: out of table entries!]\r\n"); + (void) os_sigsetmask(&oldmask, (ossigset_t *)NULL); + return 0; + } + + evr->dver_hdlr = rtn; /* Remember callback rtn */ + evr->dver_d = d; /* Remember device */ + evr->dver_ev = *evp; /* Remember event & args */ + + switch (evp->dvev_type) { + + case DVEV_DPSIG: + /* Ensure 2 args look sensible */ + sig = evp->dvev_arg.eva_int; + if (sig <= 0 || SIGMAX <= sig) { + fprintf(stderr, "[dev_evreg: Bad signal: %d]\r\n", sig); + break; /* Fail... */ + } + if (! evp->dvev_arg2.eva_ip) { + fprintf(stderr, "[dev_evreg: Bad DPSIG pointer]\r\n"); + break; /* Fail... */ + } + *(evp->dvev_arg2.eva_ip) = 0; /* Clear request flag */ + + /* If signal not already registered, add it */ + evs = &evsigtab[sig]; + if (evs->dves_sig == 0) { + + /* New, see if can install OS signal handler */ + if (osux_signal(sig, dev_sighan) == -1) { + fprintf(stderr, "[dev_evreg: Can't handle sig %d - %s]\r\n", + sig, os_strerror(-1)); + break; /* Fail... */ + } + + /* Yup, proceed with registration! */ + evs->dves_sig = sig; + INTF_INIT(evs->dves_intf); + evs->dves_reglist = NULL; + evs->dves_prev = NULL; + if (evs->dves_next = evsiglist) /* Add to head of list */ + evsiglist->dves_prev = evs; + evsiglist = evs; + } + + /* Now add handler to list for this signal */ + evr->dver_prev = NULL; + if (evr->dver_next = evs->dves_reglist) /* Add to head of list */ + evr->dver_next->dver_prev = evr; + evs->dves_reglist = evr; + + (void) os_sigsetmask(&oldmask, (ossigset_t *)NULL); + return TRUE; + + default: + /* Can't handle any other event type, break out to fail */ + break; + } + + /* Failure if get here */ + + /* Put event entry back on freelist */ + evr->dver_next = evregfree; /* Freelist is one-way, no prev */ + evregfree = evr; + (void) os_sigsetmask(&oldmask, (ossigset_t *)NULL); + return 0; +} + +/* DEV_EVUNREG - De-registers specified device completely. +** Currently only used when device is powering off, so no need +** to distinguish between events/handlers for a device. +** Note that interrupts are suspended by caller (dev_evreg). +*/ +static void +dev_evunreg(register struct device *d) +{ + register struct dvevreg_s *evr, *nextevr; + register struct dvevsig_s *evs, *nextevs; + + /* Scan all signals handled */ + for (evs = evsiglist; evs; evs = nextevs) { + nextevs = evs->dves_next; + + /* Scan all handlers registered for this signal */ + for (evr = evs->dves_reglist; evr; evr = nextevr) { + nextevr = evr->dver_next; + if (evr->dver_d == d) { /* Matching device? */ + /* Take it off list */ + if (evr->dver_prev) + evr->dver_prev->dver_next = nextevr; + else + evs->dves_reglist = evr->dver_next; /* Fix up head */ + if (nextevr) + nextevr->dver_prev = evr->dver_prev; + + /* Put on freelist */ + evr->dver_next = evregfree; /* Flist is one-way, no prev */ + evregfree = evr; + } + } + + /* Handler scan done, now see if anything left for signal to do */ + if (evs->dves_reglist == NULL) { + /* All gone, so turn off handling of signal. + ** This is probably superfluous, but tidy is as tidy do. + ** Ignore errors since some signals may be un-ignorable. + */ + osux_signal(evs->dves_sig, SIG_IGN); + + /* Take it off list */ + if (evs->dves_prev) + evs->dves_prev->dves_next = nextevs; + else + evsiglist = evs->dves_next; /* Fix up head */ + if (nextevs) + nextevs->dves_prev = evs->dves_prev; + + /* Now say it's free (uses indexed table, no freelist) */ + evs->dves_sig = 0; /* No signal in effect */ + } + } +} + + + +/* DEV_EVSHOW - Show status of event registration data +** Maybe should lock out interrupts while doing this... +*/ +int +dev_evshow(FILE *of, + char *dstr, + char *args) +{ + register struct dvevreg_s *evr; + register struct dvevsig_s *evs; + register int i; + struct dvdef_s *def; + +#if 0 /* For now, ignore any args */ + if (!dstr || strcmp(dstr, "?")==0) + /* Show general config status, etc */ + +#endif + if (of) { + fprintf(of, "Outstanding event flag: %lo\n", (long)cpu.intf_evsig); + + /* Show all registered event handlers */ + fprintf(of, "Registered event handlers:\n"); + + for (evs = evsiglist; evs; evs = evs->dves_next) { + + /* Show signal event and stuff registered for it */ + fprintf(of, " Signal: Sig %d, intf=%ld\n", + evs->dves_sig, (long)evs->dves_intf); + + /* Show all handlers registered for this signal */ + for (evr = evs->dves_reglist; evr; evr = evr->dver_next) { + fprintf(of, " Hdlr: 0x%lx (", (long)(evr->dver_hdlr)); + + /* Try to identify device */ + for (def = dvdeftab; def->dev_name; ++def) { + if (def->dev_dv == evr->dver_d) + break; + } + if (def->dev_name) + fprintf(of, "dev \"%s\"", def->dev_name); + else + fprintf(of, "unknowndev 0x%lx", + (unsigned long)(evr->dver_d)); + + /* Now show its event args in general */ + fprintf(of, ", ev{%o, 0x%lx, 0x%lx})", + i = evr->dver_ev.dvev_type, + evr->dver_ev.dvev_arg.eva_long, + evr->dver_ev.dvev_arg2.eva_long); + + /* Try to show event-specific stuff */ + if (i == DVEV_DPSIG) { + fprintf(of, " DPSIG %d, Flag=%o", + evr->dver_ev.dvev_arg.eva_int, + *(evr->dver_ev.dvev_arg2.eva_ip)); + } + fputc('\n', of); + } + } + } + return TRUE; +} + +#endif /* KLH10_EVHS_INT */ + +/* Support for asynch device attention during FE command loop + + The problem arises when using DPs for devices with mountable media. + When a user does a device (normally tape) mount or dismount at the + command loop, the command is relayed on to the DP, but the acknowledgement + of the response is normally handled by the CPU processing loop (see + apr_check().) Without an explicit call to dev_evcheck these devices + would appear to remain busy until the CPU is continued again. + + Solution: + If any command is executed that might require DP waiting, do a check + and if waiting for any DPs, turn hackery on if config/switch permits. + + If hackery enabled, during cmd input wait enable SIGALRM with + a sigaction sa_flag of SA_RESTART so as not to interfere with + any pending input. All known systems now support that. + + On each SIGALRM, call dev_evcheck(), then return quietly. + + Between each command, attempt to turn hackery off if no more + DPs are waiting. + + If calling apr_run, force hackery off. + + Plus "devwait" added for benefit of command scripts: + + devwait [dev] [n] + Wait for device DPs to become ready: + no args - all, indefinitely + [dev] - that dev only + [n] - for N seconds + + Also implement dev_status call to return actual status when desired, + eliminating the crock of using a null arg to devmount. + */ + +static int dev_dpwaiting(void); + +/* Signal handler for command-level periodic check +*/ +#if KLH10_DEV_DP +static void +dev_dpchk_sighan(int sig) +{ +#if KLH10_EVHS_INT + dev_evcheck(); +#endif +} +#endif /* KLH10_DEV_DP */ + +/* DEV_DPCHK_CTL - Turn dev-dp checking on or off. +** +** Intended use: +** After every command that affects devices: +** chkflag = dev_dpchk_ctl(TRUE); // Start checking if necessary +** Between entry of each command and its execution: +** if (chkflag) // If on, try to turn checking off +** chkflag = dev_dpchk_ctl(TRUE); +** Before calling apr_run to proceed PDP-10: +** chkflag = dev_dpchk_ctl(FALSE); // Turn checking off +*/ +int +dev_dpchk_ctl(int dochk) /* Arg TRUE to do a check */ +{ +#if KLH10_DEV_DP + static int ischecking = 0; + static ostimer_t savestate; + int ndps; + + if (dochk) { + ndps = dev_dpwaiting(); /* Find # devs waiting */ + if (ndps && !ischecking) { + /* Devs waiting and not checking, so turn checking on */ + os_timer(OS_ITIMER_REAL, dev_dpchk_sighan, + 1000000, /* One sec's worth of usec */ + &savestate); + ischecking = TRUE; + } + } else + ndps = 0; + + if (!ndps && ischecking) { + /* No devs but we're checking, so turn checking off */ + ischecking = FALSE; + os_timer_restore(&savestate); + } + return ischecking; +#else + return FALSE; +#endif /* !KLH10_DEV_DP */ +} + + +/* DEV_DPWAITING - Return some indication of the number of DPs that are + being waited for, i.e. which are not ready to accept new commands. + + Returns: + >=1 if there are any DPs waiting + -1 if there are any DPs which required a call to dev_evcheck(). + 0 if there are neither - can stop checking! + + Could either step thru all devs, or go through event reg list. + Latter probably a little faster. +*/ +static int +dev_dpwaiting(void) +{ +#if KLH10_EVHS_INT + register struct dvevsig_s *evs; + register struct dvevreg_s *evr; + register int cnt = 0; + + /* Our DP registry starts with a list of signals; check them all, + and check all handlers registered for each signal. + */ + for (evs = evsiglist; evs; evs = evs->dves_next) { + for (evr = evs->dves_reglist; evr; evr = evr->dver_next) { + /* If the handler's flag indicates it's waiting to be processed + by dev_evcheck(), call it immediately and return with + an indication that this was done. + No re-check is performed (even though it might indicate + that everything's clear) in order to avoid infinite loops for + a misbehaving device. + */ + if (*(evr->dver_ev.dvev_arg2.eva_ip)) { + dev_evcheck(); + return -1; + } + + /* Count as "waiting" any registered device for which + its comm area says "cannot send". Keep going in case + we later find a device waiting to be processed as above. + */ + if (!dp_xstest(dp_dpxto(evr->dver_d->dv_dpp))) + ++cnt; + } + } + return cnt; +#elif KLH10_DEV_DP + register struct dvdef_s *df; + + /* Grovel through entire device definition table. + ** This would be a good place to have a list of just DP devices. + */ + for (df = dvdeftab; df < &dvdeftab[KLH10_DEVMAX]; ++df) { + if (df->dev_name && df->dev_dv->dv_dpp) { + /* Count as "waiting" any device having a DP for which + its comm area says "cannot send". + */ + if (!dp_xstest(dp_dpxto(df->dev_dv->dv_dpp))) + return 1; + } + } + return 0; +#else + return 0; +#endif +} + +/* DEV_WAITING - returns TRUE if a device is waiting for its DP to respond. + */ +int +dev_waiting(FILE *of, char *dstr) +{ +#if KLH10_DEV_DP + register struct device *d; + struct dp_s *dp; + + if (!dstr || !*dstr || (*dstr == '*')) { + /* Check all devices */ + return (dev_dpwaiting() ? TRUE : FALSE); + } + if (!(d = devdpverify(of, dstr, &dp)) || !dp) + return FALSE; /* No such dev, or has no DP */ + + /* Check out specific DP. First do complete event check to remove + possibility that it might be ready but its event needs handling. + (and if nothing's waiting, can skip specific check). + */ + if (dev_dpwaiting() /* If any DPs still waiting, */ + && !dp_xstest(dp_dpxto(dp))) /* check this one. */ + return TRUE; /* DP not ready, still waiting */ +#endif /* KLH10_DEV_DP */ + return FALSE; +} diff --git a/src/kn10dev.h b/src/kn10dev.h new file mode 100644 index 0000000..cb013b8 --- /dev/null +++ b/src/kn10dev.h @@ -0,0 +1,293 @@ +/* KN10DEV.H - KLH10 Device Definitions +*/ +/* $Id: kn10dev.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: kn10dev.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* This file contains definitions needed for all device emulation code, +** whether old-style (I/O instructions) or KS-style (Unibus). +*/ + +#ifndef KN10DEV_INCLUDED +#define KN10DEV_INCLUDED 1 + +#ifdef RCSID + RCSID(kn10dev_h,"$Id: kn10dev.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +#include "klh10.h" +#include "word10.h" +#include /* For FILE stream def */ + +#include "dvuba.h" /* For ref to ubctl struct and unibus stuff */ + +#if KLH10_DEV_DP +# include "dpsup.h" /* For device subproc stuff */ +#else + struct dp_s; /* Fwd decl of anonymous struct */ +#endif + struct dvevent_s; /* Another anon fwd decl */ + +/* Basic device functionality needed. + +In the future, to allow for the possibility of dynamically loaded +libraries that can't resolve references to the main modules (ie one-way +linking), the main emulator may provide the device with a similar vector +to core functions (e.g. dv_pireq(), etc). + +On startup, main calls an initial device function which returns a pointer +to this device structure. The initial function must be specified +in some external fashion, either by predefined table or config file. + + extern struct device * (*device_create)( + FILE *out, + char *configstr); + +Devices are allowed (encouraged) to maintain their internal device +information in a larger structure that includes this one as its first +element, thus the same pointer can indicate both sets of data. + +Device drivers should be capable of handling more than one device or +unit. Static variables outside of the device structure should be +avoided. + +There are three types of output streams that might concern a driver: + + (1) Report stream - for reporting requested information back to user. + This is assumed to be part of an interactive dialogue, with + non-raw output, and the stream is provided at the time of the + call to a function. + It is not remembered across calls (unless the device wants + to do so privately for some reason). + + (2) Error stream - for unexpected errors during device and PDP-10 + operation. For now, the dv_dbf debug stream is used for this + purpose and should always be open. This stream is assumed + to be a raw-mode stream since the console will typically be + in raw mode and the universally brain-damaged unix tty support + doesn't allow different modes for different channels. So + use \r\n instead of \n. Redirecting this to a log file will + include the CRs, but so what. + + (3) Debug stream - for spontaneous trace output during device and PDP-10 + operation when dv_debug is set. While conceptually distinct, + this stream is used both for errors and for debug output. + +Issues not addressed: + Interactive stream indication? + How to specify whether user or debug output is interactive, needs + CR-LF instead of LF, or is a file? + Needs CR-LF if raw mode TTY. + Best to pass stream for each command, or leave set in structure? + Note dbf needs to be set, cuz can't find it otherwise from + the places doing debug output. + Which stream to use for actual error reporting? +*/ + + +#define KN10DEV_VERSION 2 /* Version # of structure */ + +/* Device structure. +** All entries here are set by the device, except for those marked [C] +** which are set by the higher-level CPU or Controller after device has +** created struct. +*/ +#define dv_t struct device /* Temporary local def */ +struct device { + + /* All IO */ + + int dv_vers; /* Device interface version */ + int dv_dflags; /* Device flags */ + int dv_cflags; /* [C] CPU flags */ + char *dv_name; /* [C] Device name (unique) */ + int dv_debug; /* [C] Bit flags for debugging options */ + FILE *dv_dbf; /* [C] Debug output stream */ + int dv_pireq; /* [C/D] NZ = bit for level PI req active on */ + void (*dv_pifun)(dv_t *, int); /* [C] PI request function */ + int (*dv_evreg)(dv_t *, /* [C] Event callback registration */ + void (*)(dv_t *, struct dvevent_s *), + struct dvevent_s *); + struct dp_s *dv_dpp; /* Pointer to DP struct, if one */ + + /* Controller <-> Drive communication stuff */ + + struct device *dv_ctlr; /* [C] Back-ptr to parent controller */ + int dv_num; /* [C] Unit # on parent controller */ + void (*dv_attn) (dv_t *, int); /* [C] Attention request */ + void (*dv_drerr)(dv_t *); /* [C] Drive or xfer error */ + int (*dv_iobeg)(dv_t *, int); /* [C] Xfer start */ + int (*dv_iobuf)(dv_t *, int, w10_t **); /* [C] Xfer buffer setup */ + void (*dv_ioend)(dv_t *, int); /* [C] Xfer end */ + uint32 (*dv_rdreg)(dv_t *, int); /* Drive register read */ + int (*dv_wrreg)(dv_t *, int, dvureg_t); /* Drive register write */ + + /* KA/KI/KL ("dev" IO )*/ + + w10_t (*dv_pifnwd)(dv_t *); /* PI: Get function word */ + void (*dv_cono) (dv_t *, h10_t); /* CONO 18-bit conds out */ + w10_t (*dv_coni) (dv_t *); /* CONI 36-bit conds in */ + void (*dv_datao)(dv_t *, w10_t); /* DATAO word out */ + w10_t (*dv_datai)(dv_t *); /* DATAI word in */ + + /* KS ("new" IO) */ + + struct ubctl *dv_uba; /* [C] Unibus Adapter for device */ + dvuadr_t dv_addr; /* 1st valid Unibus address */ + dvuadr_t dv_aend; /* 1st invalid Unibus address */ + int dv_brlev; /* BR setting (IRQ level= 4,5,6,7) */ + dvureg_t dv_brvec; /* BR interrupt vector setting */ + dvureg_t (*dv_pivec)(dv_t *); /* PI: Get vector */ + dvureg_t (*dv_read) (dv_t *, /* Read Unibus register */ + dvuadr_t); + void (*dv_write)(dv_t *, /* Write Unibus register */ + dvuadr_t, dvureg_t); + + /* Control functions initiated by user/operator, with + * possible feedback via stdio stream. + */ + + int (*dv_bind) (dv_t *, FILE *, /* Bind device to controller */ + dv_t *, int); + int (*dv_init) (dv_t *, FILE *); /* Final post-bind device init */ + int (*dv_exit) (dv_t *, FILE *); /* Exit, close, terminate */ + int (*dv_mount) (dv_t *, FILE *, /* Mount or unmount media */ + char *, char *); + int (*dv_help) (dv_t *, FILE *); /* Output help info */ + int (*dv_status)(dv_t *, FILE *); /* Output status info */ + int (*dv_cmd) (dv_t *, FILE *, /* General user command to device */ + char *); + + /* Misc control functions */ + + int (*dv_readin)(dv_t *, FILE *, /* Readin mode */ + w10_t, w10_t *, int); + void (*dv_powon) (dv_t *); /* "Power on" */ + void (*dv_reset) (dv_t *); /* System reset */ + void (*dv_powoff)(dv_t *); /* "Power off" */ +}; +#undef dv_t /* Was only temporary */ + +/* Flags for dv_dflags */ +#define DVFL_DEVIO 04 /* [C] Expects old-style I/O instrs */ +#define DVFL_UBIO 010 /* [C] Expects new-style I/O instrs (unibus) */ +#define DVFL_CTLIO 020 /* [D] Invoked as unit of a controller */ +#define DVFL_CTLR 040 /* [D] Controller device */ +#define DVFL_NBA 0100 /* [D] Not block addressed (== TYP 2.7) */ +#define DVFL_TAPE 0200 /* [D] Tape of some sort (== TYP 2.6) */ +#define DVFL_DISK 0400 /* [D] Random-access drive (== TYP 2.5) */ + +/* Flags for dv_debug */ +#define DVDBF_ON 01 /* Debugging on */ +#define DVDBF_DATSHO 02 /* "Show data" (if flag recognized) */ + +/* Handy debug macros */ +#define DVDEBUG(d) (((struct device *)(d))->dv_debug) +#define DVDBF(d) (((struct device *)(d))->dv_dbf) + +#define insdef_coni(rtn) \ + w10_t rtn(register struct device *d) +#define insdef_datai(rtn) \ + w10_t rtn(register struct device *d) +#define insdef_cono(rtn) \ + void rtn(register struct device *d, register h10_t erh) +#define insdef_datao(rtn) \ + void rtn(register struct device *d, register w10_t w) + +/* Exported functions */ + +extern void dev_init(void); +extern void dev_term(void); +extern int dev_drvload(FILE *, char *, char *, char *, char *); +extern int dev_define(FILE *, char *, char *, char *, char *); +extern int dev_command(FILE *, char *, char *); +extern int dev_help(FILE *, char *, char *); +extern int dev_mount(FILE *, char *, char *, char *); +extern int dev_debug(FILE *, char *, char *, char *); +extern int dev_boot(FILE *, char *, vaddr_t *); +extern int dev_show(FILE *, char *, char *); +extern int dev_status(FILE *, char *, char *); +extern int dev_waiting(FILE *, char *); +extern int dev_dpchk_ctl(int); + +extern int iodv_version(void); /* Return current ver of dev struct */ +extern int iodv_nullinit(struct device *, int); /* For initing to null dev */ +extern void iodv_setnull(struct device *); /* " " " " (old form) */ + +/* Device Event types & flags */ + +enum dveventtype { + DVEV_CLOCK=1, /* Periodic clock callout, arg is period in ticks */ + DVEV_TMOUT, /* One-shot timeout, arg is ticks */ + DVEV_1SIG, /* Sole signal */ + DVEV_NSIG, /* Multiplexed signal */ + DVEV_ASIG, /* Allocated signal */ + DVEV_DPSIG, /* Dev subproc comm signal */ + DVEV_N /* #+1 of event types */ +}; + +struct dvevent_s { + enum dveventtype dvev_type; + union dvevarg_u { + int eva_int; + long eva_long; + unsigned char *eva_ucp; + int *eva_ip; + } dvev_arg; + union dvevarg_u dvev_arg2; +}; + +/* INTERNAL definitions, not really for export to device code +*/ + +#if KLH10_EVHS_INT + +extern void dev_evinit(void); +extern void dev_evcheck(void); +extern int dev_evshow(FILE *, char *, char *); +extern int dev_evreg(struct device *, + void (*)(struct device *, struct dvevent_s *), + struct dvevent_s *); + + +/* Event registration entries */ +struct dvevreg_s { + struct dvevreg_s *dver_next, *dver_prev; /* List for variousness */ + void (*dver_hdlr)(struct device *, struct dvevent_s *); + struct device *dver_d; /* Remember its device */ + struct dvevent_s dver_ev; /* and args */ +}; +extern struct dvevreg_s *evregfree; /* Head of reg entry free list */ +extern struct dvevreg_s evregtab[]; + +struct dvevsig_s { + osintf_t dves_intf; /* Bumped when sig seen */ + struct dvevsig_s *dves_next, /* Chain on list of reg'd signals */ + *dves_prev; + int dves_sig; /* Signal # */ + struct dvevreg_s *dves_reglist; /* List of event hndlrs for this sig */ +}; +extern struct dvevsig_s *evsiglist; /* Head of reg'd signal list */ +extern struct dvevsig_s evsigtab[]; + +#endif /* KLH10_EVHS_INT */ + +#endif /* KN10DEV_INCLUDED */ diff --git a/src/kn10mac.h b/src/kn10mac.h new file mode 100644 index 0000000..4ddec03 --- /dev/null +++ b/src/kn10mac.h @@ -0,0 +1,59 @@ +/* KLH10 Macro Miscellany +*/ +/* $Id: kn10mac.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: kn10mac.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* Miscellaneous macro stuff that's sometimes useful */ + +#ifndef KN10MAC_INCLUDED +#define KN10MAC_INCLUDED 1 + +#ifdef RCSID + RCSID(kn10mac_h,"$Id: kn10mac.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* These field-hacking macros rely on twos-complement arithmetic +** properties such that given a value M, the expression (M & -M) will +** isolate the rightmost ones-bit of M, which will be a simple power +** of 2. This still isn't log2, but if M is a constant then +** a decent compiler should turn multiply and divide of this power-of-2 +** into logical shifts. +*/ + +/* FLD(uval,mask) +** Make integer field value from right-justified unsigned value +** (same as MACSYM's FLD(v,m) macro) +*/ +#define FLD(uval,mask) (((uval)*((mask)&(-(mask))))&(mask)) + +/* FLDGET(ui,mask) +** Get right-justified value from field in unsigned integer +*/ +#define FLDGET(ui,mask) (((ui)&(mask))/((mask)&(-(mask)))) + +/* FLDPUT(ui,mask,uval) +** Put right-justified unsigned value into field in integer +*/ +#define FLDPUT(ui,mask,uval) (((ui)&(~(mask)))|FLD(uval,mask)) + + +#endif /* ifndef KN10MAC_INCLUDED */ diff --git a/src/kn10ops.c b/src/kn10ops.c new file mode 100644 index 0000000..3c9eee3 --- /dev/null +++ b/src/kn10ops.c @@ -0,0 +1,4614 @@ +/* KN10OPS.C - PDP-10 arithmetic operations +*/ +/* $Id: kn10ops.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1991, 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: kn10ops.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* Contains functions to provide 36-bit PDP-10 arithmetic operations +** on a machine which does not support them. +** +** KN10OPS is primarily intended for use by the KLH10 PDP-10 emulator, but +** can also be used by KCC (PDP-10 C compiler) or other programs which want +** to emulate PDP-10 arithmetic operations. Note there are a number of +** artifacts defined here for KCC which aren't needed by the KLH10. +** +** Supported PDP-10 machine ops are "op10*()" where * is: +** AND/IOR/XOR/SETCM Single word logical operations +** MOVN/DMOVN Negation +** LSH/LSHC/ASH/ASHC/ROT/ROTC Shifting +** ADD/SUB/MUL/DIV/IDIV Single precision fixed point +** DADD/DSUB/DMUL/DDIV Double precision fixed point +** FAD/FSB/FMP/FDV Single precision floating point +** FADR/FSBR/FMPR/FDVR Single precision floating point, Round +** DFAD/DFSB/DFMP/DFDV Double precision floating point +** plus: +** NEG/DNEG Negation, single & double fixed point +** INC/DINC Increment, single & double fixed point +** CMP/DCMP Comparison (CAM*) single & double +** TST/DTST Testing (SKIP*) +*/ + +/* By default this file assumes it is being compiled in its own right +** as a module for the KLH10 emulator program. +** +** However, it can also be included in another file to provide other +** programs with their own interface to PDP-10 operation functions. +** Examples of this are KCC (PDP-10 C Compiler) and OPTEST (a program +** to test these functions). +** To do this, the macro OP10_INCLUDEFLAG must be defined prior to +** inclusion. +** +** Note: KCC should only include this if being built on a non-10 platform +** as a cross-compiler to generate PDP-10 code. +*/ + +#ifndef OP10_INCLUDEFLAG + /* Assume being compiled for KLH10 as its own module */ +# include "klh10.h" /* KLH10 Config params */ +# include "kn10def.h" /* Emulated machine defs */ + +#ifdef RCSID + RCSID(kn10ops_c,"$Id: kn10ops.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +# if 0 /* FYI: OP10_PCFSET definition from klh10.h */ +# define OP10_PCFSET(f) (cpu.mr_pcflags |= (f), \ + (((f)&(PCF_TR1|PCF_TR2)) ? INSBRKSET() : 0)) +# endif + +# if 0 /* KLH10_DEBUG */ /* OPTEST.C much better for debugging now */ + int op10debug = 0; +# define OP10_DBGPRINT(list) if (op10debug) printf list +# include +# endif + + /* Determine what values are OK for IDIVI. */ +# if KLH10_CPU_KLX /* KLX accepts +1 */ +# define OP10_IDIVI_OK(w) op10m_skipg(w) +# elif KLH10_CPU_KS /* KS accepts both +1 and -1 */ +# define OP10_IDIVI_OK(w) op10m_skipn(w) +# endif + + /* Determine whether to include G format instructions */ +# ifndef OP10_GFMT +# define OP10_GFMT KLH10_CPU_KL +# endif + + /* Determine behavior of FDV */ +# ifndef OP10_FDV_KL +# define OP10_FDV_KL (KLH10_CPU_KL || KLH10_CPU_KS) +# endif + +#endif + +/* Includer-defined macros. The following should be defined appropriately +** by either kn10def.h or the includer, if desired. They will default +** to something reasonable if not defined. +** +** OP10_PCFSET(flgs) - Must be defined if the facilities here are to return +** PC flag results; the kn10ops.h file also checks for this in order to +** define various things. +** If not defined, no flag operations are compiled. +** OP10_DBGPRINTF(list) - Defined if debug output capability is desired. +** If not defined, no debug code is compiled. +** OP10_GFMT - Defined 1 to say that G format ops should be included. +** If 0 or undefined, none are included. +** OP10_KCCOPS - Defined 1 to include emulation of KCC C ops. +** If 0 or undefined, not included. +** OP10_IDIVI_OK(w) - Defined to specify action on a failing IDIVI; test +** is made on divisor and returns TRUE if IDIVI is OK (ie should not +** set no-divide and trap flags). +** If not defined, acts like KA/KI and always fails for cases of 1 and +1. +** OP10_FDV_KL - Defined 1 to specify KL+KS behavior on FDV. +** If 0 or undefined, uses KA+KI behavior. +*/ + +#include "kn10ops.h" /* Get defs and macro facilities */ + +/* Resolve any defaults for OP10_ feature request macros */ + +#ifndef OP10_PCFSET /* If not bothering with flags, */ +# define IFFLAGS 0 +# define OP10_PCFSET(a) /* make this be no-op */ +#else /* Ah, wants flags! */ +# define IFFLAGS 1 +#endif + +/* Debug output. Invoke with nested parens, eg DEBUGPRF(("format", args)); */ +#ifndef OP10_DBGPRINT +# define DEBUGPRF(list) /* No debugging, so make no-op */ +#else +# define DEBUGPRF(list) OP10_DBGPRINT(list) +# define DBGV_W(w) (long)LHGET(w),(long)RHGET(w) +# define DBGV_D(d) DBGV_W((d).HI),DBGV_W((d).LO) +# define DBGV_Q(q) DBGV_D((q).D0),DBGV_D((q).D1) +#endif + +#ifndef OP10_GFMT /* Default is not to include G-fmt stuff */ +# define OP10_GFMT 0 +#endif +#ifndef OP10_KCCOPS /* Default is not to include KCC stuff */ +# define OP10_KCCOPS 0 +#endif + + +/* Determine whether DIV() or LDIV() is available from C */ +#if defined(__STDC__) && __STDC__ +# include /* Get div, ldiv */ + +# ifdef WORD10_ITX32 /* Try to define DIV32 operation and result */ +# if WORD10_ITX32 == WORD10_TX_INT +# define OP10_DIV32(n,d) div(n,d) +# define OP10_DIV32T div_t +# elif WORD10_ITX32 == WORD10_TX_LONG +# define OP10_DIV32(n,d) ldiv(n,d) +# define OP10_DIV32T ldiv_t +# endif +# endif + +# ifdef WORD10_ITX64 /* Try to define DIV64 operation and result */ +# if WORD10_ITX64 == WORD10_TX_INT +# define OP10_DIV64(n,d) div(n,d) +# define OP10_DIV64T div_t +# elif WORD10_ITX64 == WORD10_TX_LONG +# define OP10_DIV64(n,d) ldiv(n,d) +# define OP10_DIV64T ldiv_t +# endif +# endif + +#endif + +/* Defs for compatibility with older versions of this code */ +#define HBITS H10BITS /* Number of bits in halfword */ +#define HSIGN H10SIGN /* Sign bit for halfword */ +#define HMASK H10MASK /* Halfword Mask */ +#define OP10XWD(l,r) XWDINIT(l,r) +#define OP10DXWD(h0,h1,h2,h3) DXWDINIT(h0,h1,h2,h3) +#define OP10QXWD(h0,h1,h2,h3,l0,l1,l2,l3) QXWDINIT(h0,h1,h2,h3,l0,l1,l2,l3) +#define HI w[0] +#define LO w[1] +#define D0 d[0] +#define D1 d[1] + +/* Exported data */ +w10_t w10one = OP10XWD(0,1); +w10_t w10mask = OP10XWD(HMASK,HMASK); /* Word of all ones */ +w10_t w10zero = OP10XWD(0,0); /* Word of all zeros */ +dw10_t dw10zero = OP10DXWD(0,0,0,0); + +/* Internal data */ +static w10_t w10maxneg = OP10XWD(HSIGN,0); +static dw10_t dw10one = OP10DXWD(0,0,0,1); +static dw10_t dw10maxneg = OP10DXWD(HSIGN,0,HSIGN,0); /* FixPt */ +static qw10_t qw10maxneg = OP10QXWD(HSIGN,0,HSIGN,0, /* FixPt */ + HSIGN,0,HSIGN,0); + + +static w10_t w10loobits[37] = { +# define lmask(n) (((uint18)1<<(n))-1) +# define WBMLO(n) W10XWD(0,lmask(n)) +# define WBMHI(n) W10XWD(lmask((n)-18),H10MASK) + + WBMLO(0), WBMLO(1), WBMLO(2), WBMLO(3), WBMLO(4), WBMLO(5), + WBMLO(6), WBMLO(7), WBMLO(8), WBMLO(9), WBMLO(10), WBMLO(11), + WBMLO(12), WBMLO(13), WBMLO(14), WBMLO(15), WBMLO(16), WBMLO(17), + + WBMHI(18), WBMHI(19), WBMHI(20), WBMHI(21), WBMHI(22), WBMHI(23), + WBMHI(24), WBMHI(25), WBMHI(26), WBMHI(27), WBMHI(28), WBMHI(29), + WBMHI(30), WBMHI(31), WBMHI(32), WBMHI(33), WBMHI(34), WBMHI(35), + + W10XWD(H10MASK, H10MASK) /* Entry 36 is all ones */ +# undef WBMLO +# undef WBMHI +# undef lmask +}; + +#if 0 /* Not needed at moment */ + +static w10_t w10hiobits[37] = { +# define amask(b) ((((uint18)1<<(HBITS-(b)))-1) ^ HMASK) +# define WBMLO(n) W10XWD(amask(n), 0) +# define WBMHI(n) W10XWD(H10MASK,amask((n)-18)) + + WBMLO(0), WBMLO(1), WBMLO(2), WBMLO(3), WBMLO(4), WBMLO(5), + WBMLO(6), WBMLO(7), WBMLO(8), WBMLO(9), WBMLO(10), WBMLO(11), + WBMLO(12), WBMLO(13), WBMLO(14), WBMLO(15), WBMLO(16), WBMLO(17), + + WBMHI(18), WBMHI(19), WBMHI(20), WBMHI(21), WBMHI(22), WBMHI(23), + WBMHI(24), WBMHI(25), WBMHI(26), WBMHI(27), WBMHI(28), WBMHI(29), + WBMHI(30), WBMHI(31), WBMHI(32), WBMHI(33), WBMHI(34), WBMHI(35), + + W10XWD(H10MASK, H10MASK) /* Entry 36 is all ones */ +# undef WBMLO +# undef WBMHI +}; +static w10_t w10hizbits[37] = { +# define smask(b) (((uint18)H10MASK)>>(b)) +# define WBMLO(n) W10XWD(smask(n), H10MASK) +# define WBMHI(n) W10XWD(0,smask((n)-18)) + + WBMLO(0), WBMLO(1), WBMLO(2), WBMLO(3), WBMLO(4), WBMLO(5), + WBMLO(6), WBMLO(7), WBMLO(8), WBMLO(9), WBMLO(10), WBMLO(11), + WBMLO(12), WBMLO(13), WBMLO(14), WBMLO(15), WBMLO(16), WBMLO(17), + + WBMHI(18), WBMHI(19), WBMHI(20), WBMHI(21), WBMHI(22), WBMHI(23), + WBMHI(24), WBMHI(25), WBMHI(26), WBMHI(27), WBMHI(28), WBMHI(29), + WBMHI(30), WBMHI(31), WBMHI(32), WBMHI(33), WBMHI(34), WBMHI(35), + + W10XWD(0, 0) /* Entry 36 is all zeros */ +# undef WBMLO +# undef WBMHI +}; + +/* Halfword bit masks */ + +static uint18 h10hiobits[H10BITS+1] = { + amask(0), amask(1), amask(2), amask(3), amask(4), amask(5), + amask(6), amask(7), amask(8), amask(9), amask(10), amask(11), + amask(12), amask(13), amask(14), amask(15), amask(16), amask(17), + H10MASK /* Entry 18 is all ones */ +}; + +#endif /* 0 */ + +/* Internal routines +** Routines called x_name() are provided in order to carry out +** arithmetic operations without the side-effects (e.g. flag setting) +** of an actual instruction. +*/ +static int adffo(dw10_t); /* Arith Double FFO */ +static qw10_t x_qneg(qw10_t); /* Negate quadword */ +static qw10_t x_qash1(qw10_t); /* pseudo-ASH quadword left by 1 */ +static int x_div(dw10_t *, w10_t); /* Aux for single division */ +static int ddivstep(dw10_t *, w10_t, int, int); /* " " " " */ +static dw10_t op10dfinc(dw10_t); /* Increment DF int */ +static qw10_t x_dmul(dw10_t, dw10_t); /* For unsigned-double-int mult */ +static w10_t ffadr(w10_t, w10_t, int); /* Aux for FAD/FSB/FADR/FSBR */ +static w10_t ffmpr(w10_t, w10_t, int); /* Aux for FMP/FMPR */ +static w10_t sfnorm(int, w10_t, int); /* Float & normalize single-prec int */ +static dw10_t dfad(dw10_t, dw10_t, int); /* Aux for DFAD/DFSB */ +static int qdivstep(qw10_t *, dw10_t, int); /* For double division */ + +#if IFFLAGS +static void x_ashflg(w10_t, int); +static w10_t x_ash(w10_t, int); +static dw10_t x_ashc(dw10_t, int); +static dw10_t x_dadd(dw10_t, dw10_t); +static dw10_t x_dsub(dw10_t, dw10_t); +#else +# define x_ash(w,h) op10ash(w, (h10_t)(long)imm8op(h)) +# define x_ashc(d,h) op10ashc(d, (h10_t)(long)imm8op(h)) +# define x_dadd(d,e) op10dadd(d,e) +# define x_dsub(d,e) op10dsub(d,e) +#endif + + +/* Certain instructions use a signed 8-bit immediate operand, +** specifically ASH/C, LSH/C, ROT/C, and FSC. +** The sign bit is bit 18 (high bit of RH), applied to the low 8 bits. +** Note that a negative-signed 0 becomes -256, not 0! +** Also, these macros explicitly return an "int" result. This is +** important as they are used to convert h10_t values into ints. +*/ +#define imm8op(h) (((h)&HSIGN) ? ((int)(h)|~0377) : ((int)(h)&0377)) +#define imm8neg(h) (-((int)(h)|~0377)) /* Negate when known negative */ + + +/* Simple macros, mainly for backward compatibility. +** wskipl(w) - TRUE if word is negative (sign bit set). +** wskipn(w) - TRUE if any bits in word are set. +** wmagskipn(w) - Similar but ignores sign bit, checks magnitude only. +** dskipn(d) - TRUE if any bits in doubleword are set +** dlomagskipn(d) - Similar but ignores LOW word sign bit; HI sign +** is still checked. +*/ +#define wskipl(w) op10m_skipl(w) +#define wskipn(w) op10m_skipn(w) +#define wmagskipn(w) op10m_magstst(w) +#define dskipn(d) (wskipn((d).HI) || wskipn((d).LO)) +#define dlomagskipn(d) (wskipn((d).HI) || wmagskipn((d).LO)) + +/* Simple auxiliaries, primarily for KCC */ + +/* Convert unsigned base integer to w10_t */ +w10_t op10utow(uint32 u) +{ + register w10_t w; + + LRHSET(w, ((u >> H10BITS) & H10MASK), (u & H10MASK)); + return w; +} + +/* Convert w10_t to signed base integer */ +int32 op10wtos(register w10_t w) +{ + return ((((int32)LHGET(w))|(~H10MASK)) << HBITS) | RHGET(w); +} + + +#if 0 /* Not used, and incorrect anyway */ + +w10_t op10stow(s) /* Convert signed base integer to w10_t */ +int32 s; /* Defaults to signed! */ +{ + register w10_t w; + RHSET(w, s & HMASK); + LHSET(w, (s >> HBITS) & HMASK); + return w; +} +#endif + + +/* JFFO-type routines. +** A return value of 0 means bit 0 (sign). +** A value of 36 (or 72) means no bit found. +*/ +/* Find First One. Note returns int, not word! */ +int op10ffo(register w10_t w) +{ + register int i; + register uint18 reg; /* Need unsigned for shifting */ + + if (reg = LHGET(w)) i = 17; /* Find right halfword and set up */ + else if (reg = RHGET(w)) i = 17+18; + else return 36; + + while (reg >>= 1) --i; + return i; + +#if 0 /* Old version of this code */ + register uint32 reg; /* Need unsigned for shifting */ + if (reg = LHGET(w)) i = 0; /* Find right halfword and set up */ + else if (reg = RHGET(w)) i = 18; + else return 36; + while (!((reg <<= 1) & (H10SIGN<<1))) ++i; /* Shift until found bit */ + return i; +#endif +} + +/* ADFFO - Arithmetic Double Find First One. +** IGNORES the low word sign bit, regardless of its setting. +** Returns # of bits between high end and first one-bit. +** A return value of: +** 0 - sign bit was set +** >=36 - high word clear, bit is in low word magnitude +** >=71 - No bits were set in high word or non-sign bits of low word. +*/ +static int adffo(register dw10_t d) +{ + register int i; + register uint18 reg; /* Need unsigned for shifting */ + + if (reg = LHGET(d.HI)) i = 17; /* Find right halfword and set up */ + else if (reg = RHGET(d.HI)) i = 17+18; + else if (reg = (LHGET(d.LO)&H10MAGS)) i = 17+36-1; + else if (reg = RHGET(d.LO)) i = 17+36+18-1; + else return 36+36-1; + + while (reg >>= 1) --i; + return i; +} + +/* Logical operations and ones-complement negation */ + +w10_t op10and(register w10_t a, register w10_t b) +{ + op10m_and(a,b); + return a; +} + +w10_t op10ior(register w10_t a, register w10_t b) +{ + op10m_ior(a,b); + return a; +} + +w10_t op10xor(register w10_t a, register w10_t b) +{ + op10m_xor(a,b); + return a; +} + +w10_t op10setcm(register w10_t w) +{ + op10m_setcm(w); + return w; +} + +/* Negation - Single and Double */ + +/* MOVM - for completeness. Returns magnitude, sets flags if +** trying to make the max negative # positive. +*/ +w10_t op10movm(register w10_t w) +{ + return op10m_skipl(w) ? op10movn(w) : w; +} + +/* MOVN - If arg 0, set CRY0+CRY1. If SETZ, set TRP1+OV+CRY1. +*/ +w10_t op10movn(register w10_t w) +{ +#if IFFLAGS + op10mf_movn(w); +#else + op10m_movn(w); +#endif + return w; +} + +/* DMOVN - Note that bit 0 of low word is always cleared, to follow +** double floating-point convention. Use DNEG to follow +** the double fixed-point convention instead. +** As for MOVN, if arg 0, set CRY0+CRY1; if SETZ, set TRP1+OV+CRY1. +*/ +dw10_t +op10dmovn(register dw10_t d) +{ +#if IFFLAGS + op10mf_dmovn(d); +#else + op10m_dmovn(d); +#endif + return d; +} + +/* QNEG - artificial "instr" to negate a quad-length fixed-point +** value. Follows fixed-point convention of copying sign bit +** to low-order words. +*/ +static qw10_t x_qneg(register qw10_t q) +{ + op10m_movn(q.D1.LO); /* Negate lowest word */ + if (wmagskipn(q.D1.LO)) { + op10m_setcm(q.D1.HI); /* Low word has stuff, no carry into high */ + op10m_setcm(q.D0.LO); + op10m_setcm(q.D0.HI); + } else { + op10m_movn(q.D1.HI); /* Low word clear, so carry into high word */ + if (wmagskipn(q.D1.HI)) { /* If has stuff, */ + op10m_setcm(q.D0.LO); /* no carry into higher words */ + op10m_setcm(q.D0.HI); + } else + op10m_dmovn(q.D0); /* Lower word clear, so carry up */ + } + /* Now make sign bit uniform */ + if (wskipl(q.D0.HI)) { + op10m_signset(q.D0.LO); + op10m_signset(q.D1.HI); + op10m_signset(q.D1.LO); + } else { + op10m_signclr(q.D0.LO); + op10m_signclr(q.D1.HI); + op10m_signclr(q.D1.LO); + } + return q; +} + +/* Shift instructions (Logical and Arithmetic) */ + +#ifndef OP10_USEMAC +# define OP10_USEMAC 1 +#endif +#if OP10_USEMAC +# define LSHIFTM(w,s) op10m_lshift(w,s) +# define RSHIFTM(w,s) op10m_rshift(w,s) +#else +# define LSHIFTM(w,s) ((w) = op10lsh(w,s)) +# define RSHIFTM(w,s) ((w) = op10lsh(w,-(s))) +#endif + + +/* Logical shift */ + +w10_t op10lsh(register w10_t w, + register h10_t h) +{ + if (h & HSIGN) { /* Right shift */ + h = imm8neg(h); /* Get magnitude (was neg, so > 0) */ +#if OP10_USEMAC + op10m_rshift(w, h); +#else + if (h > HBITS) { + if (h > HBITS*2) w.rh = 0; + else w.rh = (w.lh >> (h-HBITS)); + w.lh = 0; + } else { + w.rh = (w.rh >> h) | ((w.lh << (HBITS-h)) & HMASK); + w.lh >>= h; + } +#endif + } else { /* Left shift */ + if (!(h &= 0377)) ; /* Do nothing if 0 */ + else +#if OP10_USEMAC + op10m_lshift(w, h); +#else + if (h > HBITS) { + if (h > HBITS*2) w.lh = 0; + else w.lh = (w.rh << (h-HBITS)) & HMASK; + w.rh = 0; + } else { + w.lh = ((w.lh << h)&HMASK) | (w.rh >> (HBITS-h)); + w.rh = (w.rh << h) & HMASK; + } +#endif + } + return w; +} + +/* Double Logical shift */ + +dw10_t op10lshc(register dw10_t d, + register h10_t h) +{ + if (h & HSIGN) { /* Right shift */ + h = imm8neg(h); /* Get magnitude (was neg, so > 0) */ + if (h >= HBITS*2) { + if (h > HBITS*4) op10m_setz(d.LO); + else { + d.LO = d.HI; /* Set up for macro */ + h -= HBITS*2; /* See how much farther to go */ + RSHIFTM(d.LO, h); /* Right-shift remaining distance */ + } + op10m_setz(d.HI); + } else { + register w10_t tmp; + tmp = d.HI; /* Save old value of high wd */ + RSHIFTM(d.HI, h); /* Right-shift high by amount */ + RSHIFTM(d.LO, h); /* ditto for low wd */ + h = HBITS*2 - h; /* Find left-shift for lost hi bits */ + LSHIFTM(tmp, h); /* Move into position for IOR */ + op10m_ior(d.LO, tmp); /* IOR the bits back in */ + } + } else { + if (!(h &= 0377)) ; /* Pos, do nothing if 0 */ + else if (h >= HBITS*2) { + if (h >= HBITS*4) op10m_setz(d.HI); + else { + d.HI = d.LO; /* Set up for macro */ + h -= HBITS*2; + LSHIFTM(d.HI, h); /* Do positive left-shift of word */ + } + op10m_setz(d.LO); + } else { + register w10_t tmp; + tmp = d.LO; /* Save old val of low wd */ + LSHIFTM(d.HI, h); /* Shift high word left in place */ + LSHIFTM(d.LO, h); /* Shift low word left in place */ + h = HBITS*2 - h; /* Find right-shift for lost hi bits */ + RSHIFTM(tmp, h); /* Move into position for IOR */ + op10m_ior(d.HI, tmp); /* IOR the bits back */ + } + } + + return d; +} + + +/* ASH, ASHC - If any bits of significance (a 1 if positive, 0 if negative) +** are shifted out, set overflow and Trap 1. +*/ + +/* Full-word null-bit masks, indexed by # bits for a shift +** Entry 1 has high bit set, 2 has high 2 bits, etc. +*/ + +static w10_t ashwmask[37] = { +# define amask(b) ((((uint18)1<<(HBITS-(b)))-1) ^ HMASK) +# define WBMLO(n) W10XWD(amask(n), 0) +# define WBMHI(n) W10XWD(H10MASK,amask((n)-18)) + + WBMLO(0), WBMLO(1), WBMLO(2), WBMLO(3), WBMLO(4), WBMLO(5), + WBMLO(6), WBMLO(7), WBMLO(8), WBMLO(9), WBMLO(10), WBMLO(11), + WBMLO(12), WBMLO(13), WBMLO(14), WBMLO(15), WBMLO(16), WBMLO(17), + + WBMHI(18), WBMHI(19), WBMHI(20), WBMHI(21), WBMHI(22), WBMHI(23), + WBMHI(24), WBMHI(25), WBMHI(26), WBMHI(27), WBMHI(28), WBMHI(29), + WBMHI(30), WBMHI(31), WBMHI(32), WBMHI(33), WBMHI(34), WBMHI(35), + + W10XWD(H10MASK, H10MASK) /* Entry 36 is all ones */ +}; + +/* Macro to do right-shift ASH of negative number, when shift is +** known to be 0 <= s <= 35 +*/ +#define NASH_RSHIFTM(w, s) \ + (RSHIFTM(w, s), op10m_ior(w, (ashwmask+1)[s])) + +#if IFFLAGS + /* Similar table, but for halfwords only */ +static int32 ashmask[HBITS] = { + amask(0), amask(1), amask(2), amask(3), amask(4), amask(5), + amask(6), amask(7), amask(8), amask(9), amask(10), amask(11), + amask(12), amask(13), amask(14), amask(15), amask(16), amask(17) +}; +#endif /* IFFLAGS */ +#undef amask + + +#if IFFLAGS +/* Auxiliary to set flags if shift will lose a bit */ + +static void +x_ashflg(register w10_t w, + register int shift) +{ + if (wskipl(w)) { /* If negative, */ + if (shift > 35) { /* see if shifting entire word */ + /* Always fail here, cuz 0-bits coming in from the right will + ** trigger overflow when they leave bit 1 of a negative number. + */ + OP10_PCFSET(PCF_ARO|PCF_TR1); + return; + } + op10m_setcm(w); /* invert so looking for 1-bits */ + } + if (shift >= 35) { + if (!wskipn(w)) + return; + } else if (shift >= 17) { + if (!LHGET(w) && !(RHGET(w) & ashmask[shift-17])) + return; + } else if (!(LHGET(w) & ashmask[shift+1])) + return; + OP10_PCFSET(PCF_ARO|PCF_TR1); +} +#endif /* IFFLAGS */ + +/* Arithmetic shift */ + +w10_t op10ash(register w10_t w, register h10_t h) +{ + register int i = imm8op(h); + +#if IFFLAGS + if (i > 0) + x_ashflg(w, i); /* Shifting left, so check for overflow */ + return x_ash(w, i); +} + +/* Arithmetic shift */ + +static w10_t x_ash(register w10_t w, + register int i) +{ +#endif /* IFFLAGS */ + register int32 r; /* Must be signed! */ + + if (i >= 0) { /* Left shift -- similar to logical */ + i &= 0377; + r = wskipl(w); /* Remember sign bit */ + LSHIFTM(w, i); /* Do the shift */ + if (r) op10m_signset(w); /* Set sign to same as before */ + else op10m_signclr(w); + return w; + } + /* Do right shift */ + i = -i; /* Get magnitude (was neg, so > 0) */ + if ((r = LHGET(w)) & HSIGN) + r |= ~HMASK; /* Extend sign bit of LH */ + if (i >= HBITS) { + if (i > (HBITS*2-1)) i = HBITS*2-1; + RHSET(w, ((r >> (i-HBITS)) & HMASK)); + LHSET(w, (wskipl(w) ? HMASK : 0)); + } else { + RHSET(w, ((RHGET(w) >> i) | ((LHGET(w) << (HBITS-i)) & HMASK))); + LHSET(w, ((r >> i) & HMASK)); + } + return w; +} + + +/* Double Arithmetic shift */ + +dw10_t op10ashc(register dw10_t d, + register h10_t h) +{ + register int i = imm8op(h); + +#if IFFLAGS + if (i > 0) { + if (i > 35) { + /* If high word will be completely lost, special-case it */ + i -= 35; /* Adjust for remaining code */ + if (wskipl(d.HI)) { + op10m_signset(d.LO); /* Must copy hi sign */ + op10m_setcm(d.HI); + } else + op10m_signclr(d.LO); /* Must copy hi sign */ + + /* Now test high and low words for losing non-sign bits */ + if (wskipn(d.HI)) /* Losing from high? */ + OP10_PCFSET(PCF_ARO|PCF_TR1); + else + x_ashflg(d.LO, i); /* If not, see if losing from low */ + + /* Flag testing done, do the shift */ + d.HI = x_ash(d.LO, i); /* Shift, plop result into high */ + op10m_tlz(d.LO, H10MAGS); /* Clear low except for sign */ + RHSET(d.LO, 0); + return d; /* and that's it */ + } + x_ashflg(d.HI, i); /* Losing less than a word, just check high */ + } + return x_ashc(d, i); /* Do negative or within-high positive shift */ +} + +/* Internal ASHC. +** NOTE!!! Shift argument has type signed int (not halfword!) +** and no modulo operation is applied to it. If one is needed it +** must be done to the argument prior to the call! +*/ +static dw10_t +x_ashc(register dw10_t d, + register int i) +{ +#endif /* IFFLAGS */ + + register int r; + + if (i >= 0) { /* Left shift -- similar to logical */ + if (i == 0) return d; /* If no shift, no change to operand */ + r = wskipl(d.HI); /* Remember sign bit */ + if (i >= (HBITS*2-1)) { + if (i >= (HBITS*4)-2) op10m_setz(d.HI); + else { + i -= (HBITS*2-1); + d.HI = d.LO; /* Set up for macro */ + LSHIFTM(d.HI, i); /* Do left-shift */ + } + op10m_setz(d.LO); + } else { + op10m_signclr(d.LO); /* Ensure low sign is clear */ + d.HI = op10ior(op10lsh(d.HI, i), op10lsh(d.LO, i-(HBITS*2-1))); + LSHIFTM(d.LO, i); /* Shift left in place */ + } + if (r) { /* Restore original sign */ + op10m_signset(d.HI); + op10m_signset(d.LO); + } else { + op10m_signclr(d.HI); + op10m_signclr(d.LO); + } + } else { /* Right shift -- bring in sign bit */ + r = wskipl(d.HI); /* Remember sign bit */ + i = -i; /* Get magnitude */ + if (i > (HBITS*2-1)) { + if (i > (HBITS*4-2)) d.LO = r ? w10mask : w10zero; + else d.LO = x_ash(d.HI, (HBITS*2-1)-i); + d.HI = r ? w10mask : w10zero; + } else { + op10m_signclr(d.LO); /* Ensure low sign is clear */ + d.LO = op10ior(op10lsh(d.LO, -i), x_ash(d.HI, (HBITS*2-1)-i)); + d.HI = x_ash(d.HI, -i); + if (r) + op10m_signset(d.LO); /* Set low sign if high is set */ + } + } + return d; +} + +/* X_QASH1(q) - Special semi-arithmetic shift for quadword +** Arith-shifts quadword left by 1, but does LSH in high word. +*/ +static qw10_t x_qash1(register qw10_t q) +{ + LSHIFTM(q.D1.LO, 1); /* Shift into sign bits */ + LSHIFTM(q.D1.HI, 1); + LSHIFTM(q.D0.LO, 1); + LSHIFTM(q.D0.HI, 1); + if (wskipl(q.D1.LO)) { /* Now carry anything necessary */ + op10m_signclr(q.D1.LO); + op10m_iori(q.D1.HI, 1); + } + if (wskipl(q.D1.HI)) { + op10m_signclr(q.D1.HI); + op10m_iori(q.D0.LO, 1); + } + if (wskipl(q.D0.LO)) { + op10m_signclr(q.D0.LO); + op10m_iori(q.D0.HI, 1); + } + return q; +} + + +#define rothalf(hi, lo, x) /* Shift X bits of LO up into HI */ \ + ((((hi) << (x)) | ((lo) >> (HBITS - (x)))) & HMASK) + +/* Logical Rotate */ + +w10_t op10rot(register w10_t w, register h10_t h) +{ + register h10_t tmp; + + /* Canonicalize shift arg to positive value */ + if ((h = imm8op(h)) < 0) + h = (HBITS*2) - ((-h) % (HBITS*2)); /* Right shift */ + else h %= (HBITS*2); /* Left shift */ + + if (h >= HBITS) { /* Rotating more than one halfword? */ + tmp = LHGET(w); /* Swap halves and reduce shifting */ + LHSET(w, RHGET(w)); + RHSET(w, tmp); + h -= HBITS; + } + if (h > 0) { + tmp = LHGET(w); /* Save old LH */ + /* Shift bits up into LH from RH */ + LHSET(w, rothalf(LHGET(w), RHGET(w), h)); + /* Shift bits up into RH from temp */ + RHSET(w, rothalf(RHGET(w), tmp, h)); + } + return w; +} + +/* Double Logical Rotate */ + +dw10_t op10rotc(register dw10_t d, + register h10_t h) +{ + register w10_t wtmp; + register h10_t tmp; + + /* Canonicalize shift arg to positive value */ + if ((h = imm8op(h)) < 0) + h = (HBITS*4) - ((-h) % (HBITS*4)); /* Right shift */ + else h %= (HBITS*4); /* Left shift */ + + if (h >= HBITS*2) { /* Rotating more than one word? */ + wtmp = d.HI; /* Swap words and reduce shifting */ + d.HI = d.LO; + d.LO = wtmp; + h -= HBITS*2; + } + if (h >= HBITS) { /* Rotating more than one halfword? */ + tmp = LHGET(d.HI); /* Move halves and reduce shifting */ + LHSET(d.HI, RHGET(d.HI)); + RHSET(d.HI, LHGET(d.LO)); + LHSET(d.LO, RHGET(d.LO)); + RHSET(d.LO, tmp); + h -= HBITS; + } + if (h > 0) { + tmp = LHGET(d.HI); /* Save high halfwd */ + /* Shift into LH from RH */ + LHSET(d.HI, rothalf(LHGET(d.HI), RHGET(d.HI), h)); + /* Shift into RH from lo LH */ + RHSET(d.HI, rothalf(RHGET(d.HI), LHGET(d.LO), h)); + /* Shift into lo LH fm lo RH */ + LHSET(d.LO, rothalf(LHGET(d.LO), RHGET(d.LO), h)); + /* Shift into lo RH fm temp */ + RHSET(d.LO, rothalf(RHGET(d.LO), tmp, h)); + } + return d; +} + +/* CIRC - Special ITS instruction! +** Like ROTC except bits are shifted in and out of 2nd AC in the +** opposite direction, thus CIRC 1,44 leaves in AC 2 the reverse of +** what AC 1 contained. +** (Alan: CIRC behaves exactly like ROTC except you reverse AC+1 before and after the + operation. The behavior when count > 36. may well differ between + KA/KI/KL/KS, but will be consistent with LSH, ROT, etc.) +*/ +static h10_t hrev(h10_t); + +/* Double Logical Circulate */ + +dw10_t op10circ(register dw10_t d, + register h10_t h) +{ + register w10_t wtmp; + register h10_t tmp; + + /* Canonicalize shift arg to positive value */ + if ((h = imm8op(h)) < 0) + h = (HBITS*4) - ((-h) % (HBITS*4)); /* Right shift */ + else h %= (HBITS*4); /* Left shift */ + + /* Reduce to halfword case by circ'ing halfword chunks */ + if (h >= HBITS*3) { /* Circ 54+n where (0 <= n < 18) */ + tmp = RHGET(d.HI); + RHSET(d.HI, LHGET(d.HI)); + LHSET(d.HI, hrev((h10_t) LHGET(d.LO))); + LHSET(d.LO, RHGET(d.LO)); + RHSET(d.LO, hrev(tmp)); + h -= HBITS*3; + } else if (h >= HBITS*2) { /* Circ 36+n where (0 <= n < 18) */ + wtmp = d.HI; + LHSET(d.HI, hrev((h10_t) RHGET(d.LO))); + RHSET(d.HI, hrev((h10_t) LHGET(d.LO))); + LHSET(d.LO, hrev((h10_t) RHGET(wtmp))); + RHSET(d.LO, hrev((h10_t) LHGET(wtmp))); + h -= HBITS*2; + } else if (h >= HBITS) { /* Circ 18+n where (0 <= n < 18) */ + tmp = LHGET(d.HI); + LHSET(d.HI, RHGET(d.HI)); + RHSET(d.HI, hrev((h10_t) RHGET(d.LO))); + RHSET(d.LO, LHGET(d.LO)); + LHSET(d.LO, hrev(tmp)); + h -= HBITS; + } + + if (h > 0) { /* Circ n where (0 <= n < 18) */ + tmp = LHGET(d.HI); /* Save high halfwd */ + /* Shift into from */ + LHSET(d.HI, rothalf(LHGET(d.HI), RHGET(d.HI), h)); + /* hi LH hi RH */ + RHSET(d.HI, rothalf(RHGET(d.HI), hrev((h10_t) RHGET(d.LO)), h)); + /* hi RH rev lo RH */ + RHSET(d.LO, rothalf(LHGET(d.LO), RHGET(d.LO), 18-h)); + /* lo RH lo LH */ + LHSET(d.LO, rothalf(hrev(tmp), LHGET(d.LO), 18-h)); + /* lo LH rev hi LH */ + } + return d; +} + +/* HREV - reverse bits in halfword */ +static h10_t hrev(register h10_t h) +{ + register h10_t r; + register int i; + + if (!h) return 0; + for (r = 0, i = 18; --i >= 0; ) { + r = (r << 1) | (h & 01); /* Transfer low bit to high */ + if (!(h >>= 1)) /* Check for early escape */ + return r << i; + } + return r; +} +#undef rothalf + +/* Fixed-point PC Flag facilities */ + +/* + The algorithm used here to set the carry and overflow flags +is basically table-driven by the three sign bits of the two operands and +their result. + Subtraction uses a different table; although one can get a correct +value simply by negating the second operand and adding it in, that procedure +does not set the flags properly. + + A + B = S C0 C1 OVFL + TRAP1 + --------- ----- ------------ +[0] 0 0 0 - - +[1] 0 0 1 - X AR0+TR1 +[2] 0 1 0 X X +[3] 0 1 1 - - +[4] 1 0 0 X X +[5] 1 0 1 - - +[6] 1 1 0 X - AR0+TR1 +[7] 1 1 1 X X + + A - B = S C0 C1 OVFL + TRAP1 + --------- ----- ------------ +[0] 0 0 0 X X +[1] 0 0 1 - - +[2] 0 1 0 - - +[3] 0 1 1 - X AR0+TR1 +[4] 1 0 0 X - AR0+TR1 +[5] 1 0 1 X X +[6] 1 1 0 X X +[7] 1 1 1 - - + +*/ + + +/* Define common macro for setting result flags of add operations. +** Requires that the original operands still be available. +*/ +#define ADDFLAGS(sum, a, b) \ + if (wskipl(a)) { \ + if (wskipl(b)) { /* If both summands neg */\ + if (wskipl(sum)) /* Always have a carry */\ + OP10_PCFSET(PCF_CR0+PCF_CR1); \ + else /* Sum pos, overflowed */\ + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_CR0); \ + } else if (!wskipl(sum)) /* Mixed signs, so expect 1 */\ + OP10_PCFSET(PCF_CR0+PCF_CR1); /* Sum pos, carried out */\ + } else { \ + if (!wskipl(b)) { /* If both summands pos */\ + if (wskipl(sum)) /* Sum shd be positive too */\ + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_CR1); \ + } else if (!wskipl(sum)) /* Mixed signs, so expect 1 */\ + OP10_PCFSET(PCF_CR0+PCF_CR1); /* Sum pos, carried out */\ + } + +/* Define common macro for setting result flags of subtract operations. +** Requires that the original operands still be available. +*/ +#define SUBFLAGS(sum, a, b) \ + if (!wskipl(a)) { /* Check 0-3? */\ + if (!wskipl(b)) { /* Check 0-1? */\ + if (!wskipl(sum)) /* */\ + OP10_PCFSET(PCF_CR0+PCF_CR1); /* [0] */\ + } else { /* [1] is nop */\ + if (wskipl(sum)) /* */\ + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_CR1); /* [3] */\ + } /* [2] is nop */\ + } else { /* Check 4-7 */\ + if (!wskipl(b)) { /* Check 4-5 */\ + if (!wskipl(sum)) /* [4] or [5] */\ + OP10_PCFSET(PCF_CR0+PCF_ARO+PCF_TR1); /* [4] */\ + else \ + OP10_PCFSET(PCF_CR0+PCF_CR1); /* [5] */\ + } else { \ + if (!wskipl(sum)) /* [6] or [7] */\ + OP10_PCFSET(PCF_CR0+PCF_CR1); /* [6] */\ + } /* [7] is nop */\ + } + + +/* Fixed-point Single-precision arithmetic */ + + +w10_t op10add(register w10_t a, register w10_t b) +{ +#if IFFLAGS + register w10_t sum; + + sum = a; + op10m_add(sum, b); /* Add the words */ + ADDFLAGS(sum, a, b) /* Invoke macro to set PC flags */ + return sum; +#else + op10m_add(a,b); + return a; +#endif +} + +w10_t op10inc(register w10_t a) +{ + op10mf_inc(a); + return a; +} + +w10_t op10sub(register w10_t a, register w10_t b) +{ +#if IFFLAGS + register w10_t sum; + + sum = a; + op10m_sub(sum, b); /* Subtract the words */ + SUBFLAGS(sum, a, b) /* Invoke macro to set PC flags */ + return sum; +#else + op10m_sub(a,b); + return a; +#endif +} + +w10_t op10imul(register w10_t a, register w10_t b) +{ + register dw10_t d; + register int sign; + + if (sign = wskipl(a)) /* Make args positive, remember signs */ + op10m_movn(a); + if (wskipl(b)) { + sign = !sign; + op10m_movn(b); +#if IFFLAGS + /* Check for screw case */ + if (wskipl(b) && wskipl(a)) { /* If max neg * max neg, */ + OP10_PCFSET(PCF_ARO+PCF_TR1); + return b; /* Return low word (max neg) */ + } +#endif + } + d = op10xmul(a, b); /* Do unsigned multiply, get double product */ + + /* For IMUL, result must fit into one word, or overflow is set. + ** Can check most efficiently here, while value is still positive, + ** for a high-word of 0 - this will win 99.99% of the time. + ** Just need to be careful of the 1 ? 0 case, which will fit if negated. + */ +#if IFFLAGS + if (wskipn(d.HI) + && (!sign || op10m_cain(d.HI, 1) || wskipn(d.LO))) + OP10_PCFSET(PCF_ARO+PCF_TR1); +#endif + + if (sign) /* Negate result if necessary */ + op10m_dneg(d); /* Yup, negate in place, fixed-point style */ + return d.LO; /* Return low-order result */ +} + +dw10_t op10mul(register w10_t a, register w10_t b) +{ + register dw10_t d; + register int sign; + + if (sign = wskipl(a)) + op10m_movn(a); + if (wskipl(b)) { + sign = !sign; + op10m_movn(b); +#if IFFLAGS + if (wskipl(b) && wskipl(a)) { + OP10_PCFSET(PCF_ARO+PCF_TR1); + return dw10maxneg; /* Return max double negative */ + } +#endif + } + d = op10xmul(a, b); /* Do the multiply */ + if (sign) /* Negate result if necessary */ + op10m_dneg(d); /* Yup, negate in place, fixed-point style */ + return d; +} + +#define DIGBTS 16 +#define DIGMSK (((uint32)1<> (HBITS-(36-(2*DIGBTS))); \ + v[1] = ((LHGET(w) << (18-DIGBTS)) & DIGMSK) | (RHGET(w) >> DIGBTS); \ + v[2] = RHGET(w) & DIGMSK; \ + DEBUGPRF(("Array: %lo %lo %lo\n", (long)v[0], (long)v[1], (long)v[2])); + setvec(a, av); + setvec(b, bv); +/* pv[0] = pv[1] = pv[2] = */ pv[3] = pv[4] = pv[5] = 0; + + /* Now multiply the vectors together. Because we have 32-bit unsigned + ** integers, we can hold the result of multiplying 2 16-bit integers. + */ + for (ai = NDIGS(36); --ai >= 0;) { + register uint32 hicarry = 0; + for (bi = NDIGS(36); --bi >= 0;) { +#if 0 + register uint32 prod, high; + prod = av[ai] * bv[bi]; + high = prod >> DIGBTS; + prod &= DIGMSK; + DEBUGPRF(("%d*b%d: %lo,%lo + %lo + p%d => ", + ai,bi,high,prod, hicarry, ai+bi+1)); + hicarry += prod + pv[ai+bi+1]; + pv[ai+bi+1] = hicarry & DIGMSK; + hicarry = (hicarry>>DIGBTS) + high; + DEBUGPRF(("p%d: %lo, carry %lo\n", ai+bi+1, pv[ai+bi+1], hicarry)); +#else + hicarry += av[ai] * bv[bi] + pv[ai+bi+1]; + pv[ai+bi+1] = hicarry & DIGMSK; + hicarry >>= DIGBTS; +#endif + } + pv[ai] = hicarry; + } + + /* Now put together doubleword from 71-bit product vector. + ** Have to leave low-order sign bit clear. + */ + DEBUGPRF(("ArOut: %lo %lo %lo %lo %lo %lo\n", + (long)pv[0], (long)pv[1], (long)pv[2], + (long)pv[3], (long)pv[4], (long)pv[5])); + +#if DIGBTS==16 + RHSET(d.LO, (pv[4] & ((1<<2)-1))<< (18-2) | pv[5] ); + LHSET(d.LO, (pv[3] & ((1<<3)-1))<< (17-3) | (pv[4] >> 2) ); + RHSET(d.HI, (pv[2] & ((1<<5)-1))<< (18-5) | (pv[3] >> 3) ); + LHSET(d.HI, (pv[1] & ((1<<7)-1))<< (18-7) | (pv[2] >> 5) ); +#elif DIGBTS==15 + RHSET(d.LO, (pv[4] & ((1<< 3)-1))<< (18-3) | pv[5] ); + LHSET(d.LO, (pv[3] & ((1<< 5)-1))<< (17-5) | (pv[4] >> 3) ); + RHSET(d.HI, (pv[2] & ((1<< 8)-1))<< (18-8) | (pv[3] >> 5) ); + LHSET(d.HI, (pv[1] & ((1<<11)-1))<< (18-11) | (pv[2] >> 8) ); +#endif + return d; +} + +/* IDIV and DIV. Note that if these routines are used for instruction +** emulation, some provision must be made for aborting the instruction +** if No Divide is set -- so that I/DIVM and I/DIVB won't clobber the +** memory operand. +** X(I)DIV exists for this purpose. Unlike the usual OP10x routines, its first +** arg is a pointer (through which it fetches operand and stores value) +** and it returns a zero int if the division failed. +** IDIV may need to do a special check for 1<<35 / -1 depending on how +** accurately we want to emulate specific processors. +** The KS10 result is 1<<35 with no error; others produce overflow. +** On KA, KI, and some KLs (1<<35)/1 also overflows. See proc ref man. +*/ +dw10_t op10idiv(w10_t a, w10_t b) +{ + dw10_t d; + d.LO = a; + d.HI = wskipl(a) ? w10mask : w10zero; + if (!x_div(&d, b)) { + d.HI = a; + op10m_setz(d.LO); +#ifdef OP10_IDIVI_OK + if (!OP10_IDIVI_OK(b)) +#endif + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_DIV); + } + return d; +} + +/* Note returns doubleword! High is quotient */ +dw10_t op10div( + dw10_t d, /* Dividend */ + register w10_t w) /* Divisor */ +{ + if (!x_div(&d, w)) + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_DIV); + return d; +} + +int op10xidiv(dw10_t *ad, + w10_t a, + w10_t b) +{ + register int r; + + ad->LO = a; + ad->HI = wskipl(a) ? w10mask : w10zero; + if (r = x_div(ad, b)) + return r; /* Success */ + + /* Failure is only possible if A is 1<<35 and B is 1, 0, or -1. + ** B=0 always causes No Divide. + ** B=1 is OK on the KS and multi-section KLs. + ** B=-1 is OK on the KS only. + */ +#ifdef OP10_IDIVI_OK + if (OP10_IDIVI_OK(b)) { + ad->HI = a; + ad->LO = w10zero; + return 1; + } +#endif /* OP10_IDIVI_OK */ + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_DIV); + return 0; +} + +int op10xdiv(dw10_t *ad, /* Dividend */ + register w10_t w) /* Divisor */ +{ + register int r; + return (r = x_div(ad, w)) ? r : +#if IFFLAGS + (OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_DIV), 0); +#else + (0); +#endif +} + +/* X_DIV - Internal single-precision division. +** +** NOTE: There is a discrepancy between the PRM and what an actual KLX does. +** It appears that if the resulting quotient would be the largest +** negative number (400000,,0) then certain operands are permitted +** without causing a no-divide condition. +** +** Specifically, args like [1 ? 0] / [-1] result in SETZ even +** though this is explicitly prohibited by PRM p.2-14 which says +** "If the high order word of the magnitude of the double length +** number in AC,AC+1 is greater than or *EQUAL TO* the magnitude of +** the operand specified by M, set Trap 1, ..." +** +** This sort of makes sense as the result is arithmetically correct; +** the max neg # is one less in magnitude than the max positive # so +** this one case is allowed to escape. +** +** The crucial code is in ddivstep where it checks for the above condition. +** Unfortunately at that point it doesn't know anything about the ultimate +** sign of the result and so can't tell whether to special-case equality +** or not. At least, it can't without the stupid extra argument passed +** as the "qsign" parameter. +** +** FDVR, which uses ddivstep also, doesn't seem to suffer from this +** inconsistency. DDIV, which uses qdivstep but otherwise is conceptually +** identical, doesn't either -- that is, it follows the PRM. Go figure. +*/ +/* Note returns doubleword! High is quotient */ +static int x_div( + dw10_t *ad, /* Dividend */ + register w10_t w) /* Divisor */ +{ + dw10_t d; + int numsign, densign; + + d = *ad; /* Don't touch original arg in case we fail */ + + /* Make args positive. Note that if either arg is the max neg number + ** the result will be that same number. Don't bother checking here + ** as the initial dividend/divisor comparison test should catch them. + */ + if (numsign = wskipl(d.HI)) + op10m_dmovn(d); /* Make positive (unless 1<<35) */ + if (densign = wskipl(w)) + op10m_movn(w); /* Get absolute value, ignore max neg */ + + /* Check for zero divisor, otherwise initial test could pass if + ** dividend high word was 1<<35. Sigh. + */ + if (!wskipn(w)) + return 0; /* Fail, no divide */ + + /* Quick method - see if can do division in native arithmetic */ + if (!wskipn(d.HI) && !(LHGET(d.LO)&(~(HMASK>>4))) + && !(LHGET(w)&(~(HMASK>>4)))) { + register uint32 num, den; + num = op10wtos(d.LO); + den = op10wtos(w); + d.HI = op10utow(num/den); + d.LO = op10utow(num%den); + DEBUGPRF(("idivquick: %o/%o Q: %lo,%lo R: %lo,%lo\n", + numsign, densign, DBGV_W(d.HI), DBGV_W(d.LO))); + } else { + /* Ugh, hack big numbers */ + /* Divide them, get double result */ + if (!ddivstep(&d, w, 35, numsign != densign)) + return 0; /* Fail, no divide */ + + /* Fix up remainder */ + if (wskipl(d.LO)) /* If final remainder was negative */ + op10m_add(d.LO, w); /* add divisor back in */ + DEBUGPRF(("idivslow: %o/%o Q: %lo,%lo R: %lo,%lo\n", + numsign, densign, DBGV_W(d.HI), DBGV_W(d.LO))); + } + + /* Unsigned division done, now fix up results */ + if (numsign) /* Remainder has same sign as dividend */ + op10m_movn(d.LO); /* Store remainder */ + + /* Now fix up quotient if dividend & divisor had different sign */ + if (numsign != densign) + op10m_movn(d.HI); + *ad = d; + return 1; +} + +static int +ddivstep(dw10_t *ad, + register w10_t w, + register int nmagbits, + int qsign) /* TRUE if quotient will ultimately be negative */ +{ + register dw10_t d; + register w10_t q; + register int qbit; + + d = *ad; + + DEBUGPRF(("ddivstep(%d) D: %lo,%lo,%lo,%lo W: %lo,%lo qsign %s\n", + nmagbits, DBGV_D(d), DBGV_W(w), (qsign ? "TRUE" : "FALSE"))); + + /* Apply initial test - high order wd must be less than abs val of + ** divisor, else overflow & no divide. + */ + op10m_sub(d.HI, w); /* Subtract divisor from high word */ + if (!wskipl(d.HI)) { /* See if divisor was larger or equal */ + /* Ugh, enter Land of the Special Case. + ** According to the PRM this should always cause No Divide, but + ** on the KLX at least, this is okay provided the final quotient + ** has the maximum negative value (400000,,0). + ** This is the only reason for "qsign". Sigh. + ** Note that for this case, the remainder will always be the low + ** word as-is (with sign cleared, so fixup done by x_div will + ** do the right thing). + */ + if (qsign && !wskipn(d.HI)) { + op10m_signclr(d.LO); /* Flush low sign for test & rem */ + /* Now one last check - can't have any more quotient bits, + ** so make sure rest of dividend is less than divisor, i.e. it + ** all goes into the remainder. + */ + if (op10m_ucmpl(d.LO, w)) { + LHSET(d.HI, H10SIGN); /* Make quotient be max neg #! */ + *ad = d; + return 1; /* And return success! */ + } + } + return 0; /* Overflow, no divide */ + } + + /* First quotient bit (future sign bit) is always 0 at this point. */ + qbit = 0; /* Set first quotient bit */ + op10m_setz(q); /* Clear current & future signs */ + LSHIFTM(d.LO, 1); /* Squeeze out low sign bit */ + + while (--nmagbits >= 0) { /* Loop N times for N magnitude bits */ + /* Do a LSHC d,1 to shift new bit into dividend */ + LSHIFTM(d.HI, 1); + if (wskipl(d.LO)) /* If high bit set in low wd, */ + op10m_iori(d.HI, 1); /* shift into low bit of high */ + LSHIFTM(d.LO, 1); + + if (qbit) op10m_sub(d.HI, w); /* Sub or add, per previous qbit */ + else op10m_add(d.HI, w); + qbit = (wskipl(d.HI) ? 0 : 1); /* Get new quotient bit */ + LSHIFTM(q, 1); /* Shift quotient over, add bit */ + op10m_iori(q, qbit); + DEBUGPRF(("ddivstep %d: D: %lo,%lo,%lo,%lo Q: %lo,%lo \n", + nmagbits, DBGV_D(d), DBGV_W(q))); +#if 0 + /* Minor speed hack; works, but worth the overhead of zero-testing? */ + if (!wskipn(d.HI) && !wskipn(d.LO)) { + /* Special copout if hit zero early */ + LSHIFTM(d.HI, nmagbits); + LSHIFTM(q, nmagbits); + break; + } +#endif + } + + ad->LO = d.HI; /* Return quotient & remainder in right places */ + ad->HI = q; + return 1; +} + +/* Fixed-point Double-precision arithmetic */ + +dw10_t op10dadd(register dw10_t da, register dw10_t db) +{ +#if IFFLAGS + register w10_t sum; + + op10m_signclr(da.LO); /* Must ignore signs of low words */ + op10m_signclr(db.LO); + op10m_add(da.LO, db.LO); /* Add low B to low A */ + + /* The following is a near-duplicate of the code for op10add. + ** It has to be replicated, instead of just calling op10add, in order to + ** properly handle the carry from the low word addition without + ** losing track of what the flags should be. + */ + sum = da.HI; /* Set up high sum */ + if (wskipl(da.LO)) /* If low sign set, carry to high sum! */ + op10m_inc(sum); + op10m_add(sum, db.HI); /* Add high words */ + + ADDFLAGS(sum, da.HI, db.HI) /* Check for and set PC flags */ + + da.HI = sum; /* OK, put sum back in place */ + if (wskipl(da.HI)) /* Copy sign bit into low word of result */ + op10m_signset(da.LO); + else op10m_signclr(da.LO); + return da; +} + +static dw10_t +x_dadd(register dw10_t da, register dw10_t db) +{ +#endif /* IFFLAGS */ + + op10m_signclr(da.LO); /* Must ignore signs of low words */ + op10m_signclr(db.LO); + op10m_add(da.LO, db.LO); /* Add low B to low A */ + if (wskipl(da.LO)) /* If sign set, carry to high */ + op10m_inc(da.HI); + op10m_add(da.HI, db.HI); /* Add high words */ + if (wskipl(da.HI)) /* Copy sign bit into low word of result */ + op10m_signset(da.LO); + else op10m_signclr(da.LO); + return da; +} + +/* OP10DINC - Increment double-prec fixed-point. */ + +dw10_t op10dinc(register dw10_t d) +{ + op10m_inc(d.LO); + if (!wmagskipn(d.LO)) { /* Check for carry to high word */ + op10m_inc(d.HI); /* Yep, bump high word */ + LHSET(d.LO, (LHGET(d.HI) & HSIGN)); /* Copy sign bit to low */ + } + return d; +} + +/* OP10DFINC - Increment double-prec floating-point (internal) +*/ +static dw10_t op10dfinc(register dw10_t d) +{ + op10m_inc(d.LO); + if (!wmagskipn(d.LO)) { /* Check for carry to high word */ + op10m_signclr(d.LO); /* Yep, clear low sign */ + op10m_inc(d.HI); /* And, bump high word */ + } + return d; +} + + +dw10_t op10dsub(register dw10_t da, register dw10_t db) +{ +#if IFFLAGS + register w10_t sum; + +#if 1 + op10m_signclr(da.LO); /* Must ignore signs of low words */ + op10m_signclr(db.LO); + op10m_sub(da.LO, db.LO); /* Sub low B from low A */ + sum = da.HI; /* Set up high result word */ + if (wskipl(da.LO)) /* If low sign set, carry from high sum! */ + op10m_dec(sum); + op10m_sub(sum, db.HI); /* Subtract high words */ + + SUBFLAGS(sum, da.HI, db.HI) /* Check for and set PC flags */ + + da.HI = sum; /* OK, put sum back in place */ + if (wskipl(da.HI)) /* Copy sign bit into low word of result */ + op10m_signset(da.LO); + else op10m_signclr(da.LO); + return da; + +#else + op10m_movn(db.LO); /* Negate low word */ + if (wmagskipn(db.LO)) /* See if stuff, ignore sign bit */ + op10m_setcm(db.HI); /* Low word has stuff, no carry into high */ + else op10m_movn(db.HI); /* Low word clear, so carry into high word */ + return op10dadd(da, db); /* Now add da + (-db) */ +#endif +} + +static dw10_t +x_dsub(register dw10_t da, register dw10_t db) +{ +#endif /* IFFLAGS */ + op10m_dmovn(db); /* Negate 2nd operand in place */ + return x_dadd(da, db); /* and add in */ +} + + +qw10_t op10dmul(register dw10_t da, register dw10_t db) +{ + register int sign; + + if (sign = wskipl(da.HI)) + op10m_dneg(da); + if (wskipl(db.HI)) { + sign = !sign; + op10m_dneg(db); +#if IFFLAGS + if (wskipl(db.HI) && wskipl(da.HI)) { + OP10_PCFSET(PCF_ARO+PCF_TR1); + return qw10maxneg; /* Return max neg quadword */ + } +#endif + } + return sign ? x_qneg(x_dmul(da, db)) : x_dmul(da, db); +} + +/* Unsigned double-int multiply */ + +static qw10_t x_dmul(register dw10_t da, register dw10_t db) +{ + register qw10_t q; /* Quad-length result */ + register int ai, bi; + uint32 av[5], bv[5], pv[10]; + + /* First get 71-bit args into vectors of 16-bit integers. + ** The main screw is ignoring the sign bit of low-order word. + */ +#define dsetvec(dw,v) \ + v[0] = LHGET(dw.HI) >> 11; /* High 7 bits of LH */\ + v[1] = (LHGET(dw.HI) & ((1<<11)-1))<<5 | (RHGET(dw.HI) >> 13); \ + v[2] = (RHGET(dw.HI) & ((1<<13)-1))<<3 | ((LHGET(dw.LO) >> 14) & 07); \ + v[3] = (LHGET(dw.LO) & ((1<<14)-1))<<2 | (RHGET(dw.LO) >> 16); \ + v[4] = RHGET(dw.LO) & (((h10_t)1<<16)-1); \ + DEBUGPRF(("Array: %lo %lo %lo %lo %lo\n", \ + (long)v[0],(long)v[1],(long)v[2],(long)v[3],(long)v[4])); + + dsetvec(da, av); + dsetvec(db, bv); + for (ai = 10; --ai >= 0; ) /* Clear product vector */ + pv[ai] = 0; + + /* Now multiply the vectors together. Because we have 32-bit unsigned + ** integers, we can hold the result of multiplying 2 16-bit integers. + */ + for (ai = 5; --ai >= 0;) { + register uint32 hicarry = 0; + for (bi = 5; --bi >= 0;) { +#if 1 + register uint32 prod, high; + prod = av[ai] * bv[bi]; + high = prod >> DIGBTS; + prod &= DIGMSK; + DEBUGPRF(("%d*b%d: %lo,%lo + %lo + p%d => ", + ai,bi, (long)high, (long)prod, (long)hicarry, ai+bi+1)); + hicarry += prod + pv[ai+bi+1]; + pv[ai+bi+1] = hicarry & DIGMSK; + hicarry = (hicarry>>DIGBTS) + high; + DEBUGPRF(("p%d: %lo, carry %lo\n", + ai+bi+1, (long)pv[ai+bi+1], (long)hicarry)); +#else + hicarry += av[ai] * bv[bi] + pv[ai+bi+1]; + pv[ai+bi+1] = hicarry & DIGMSK; + hicarry >>= DIGBTS; +#endif + } + pv[ai] = hicarry; + } + + /* Now put together quadword from low 140+1 bits of product vector. + ** Screw, again, is skipping over sign bits of low-order words. + */ + DEBUGPRF(("ArOut: %lo %lo %lo %lo %lo %lo %lo %lo %lo %lo\n", + (long)pv[0], (long)pv[1], (long)pv[2], (long)pv[3], + (long)pv[4], (long)pv[5], (long)pv[6], (long)pv[7], + (long)pv[8], (long)pv[9])); + + LHSET(q.D0.HI, (pv[1] & ((1<<13)-1))<< (18-13) | (pv[2] >> (16-(18-13))) ); + RHSET(q.D0.HI, (pv[2] & ((1<<11)-1))<< (18-11) | (pv[3] >> (16-(18-11))) ); + LHSET(q.D0.LO, (pv[3] & ((1<< 9)-1))<< (18-10) | (pv[4] >> (16-(18-10))) ); + RHSET(q.D0.LO, (pv[4] & ((1<< 8)-1))<< (18- 8) | (pv[5] >> (16-(18- 8))) ); + LHSET(q.D1.HI, (pv[5] & ((1<< 6)-1))<< (18- 7) | (pv[6] >> (16-(18- 7))) ); + RHSET(q.D1.HI, (pv[6] & ((1<< 5)-1))<< (18- 5) | (pv[7] >> (16-(18- 5))) ); + LHSET(q.D1.LO, (pv[7] & ((1<< 3)-1))<< (18- 4) | (pv[8] >> (16-(18- 4))) ); + RHSET(q.D1.LO, (pv[8] & ((1<< 2)-1))<< (18- 2) | pv[9] ); + + /* Must duplicate sign bit in all low-order words. */ + if (wskipl(q.D0.HI)) { + op10m_signset(q.D0.LO); + op10m_signset(q.D1.HI); + op10m_signset(q.D1.LO); + } + return q; +} + +/* Note returns quadword! High double is quotient */ + +qw10_t op10ddiv( + qw10_t qw, /* Dividend */ + register dw10_t d) /* Divisor */ +{ + qw10_t origq; + int numsign, densign; + + origq = qw; /* Save original arg in case of error */ + + /* Make args positive. Note that if either arg is the max neg number + ** the result will be that same number. Don't bother checking here + ** as the initial dividend/divisor comparison test should catch them. + */ + if (numsign = wskipl(qw.D0.HI)) + qw = x_qneg(qw); + if (densign = wskipl(d.HI)) + op10m_dmovn(d); + + /* Check for zero divisor */ + if (!dlomagskipn(d)) { + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_DIV); + return origq; /* Overflow, no divide */ + } + + /* See if can do simpler division */ + if (!dlomagskipn(qw.D0) && !wmagskipn(qw.D1.HI) && !wskipn(d.HI)) { + op10m_signclr(qw.D1.HI); /* Ensure sign is off in low words */ + op10m_signclr(d.LO); + DEBUGPRF(("ddivquick: %o/%o N: %lo,%lo,%lo,%lo D: %lo,%lo\n", + numsign, densign, DBGV_D(qw.D1), DBGV_W(d.LO))); + + if (!x_div(&qw.D1, d.LO)) { /* Do single-prec division */ + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_DIV); + return origq; /* Overflow, no divide */ + } + qw.D0.LO = qw.D1.HI; /* Quotient here, qw.D0.HI already 0 */ + op10m_setz(qw.D1.HI); /* Remainder already in qw.D1.LO */ + } else { + /* Ugh, must hack big numbers */ + if (!qdivstep(&qw, d, 70)) { /* Divide, get 70 bits */ + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_DIV); + return origq; /* Overflow, no divide */ + } + /* Fix up remainder. Always has same sign as dividend. */ + if (wskipl(qw.D1.HI)) /* If final result was negative */ + qw.D1 = x_dadd(qw.D1, d); /* add divisor back in */ + } + DEBUGPRF(("ddiv: %o/%o Q: %lo,%lo,%lo,%lo R: %lo,%lo,%lo,%lo\n", + numsign, densign, DBGV_D(qw.D0), DBGV_D(qw.D1))); + + /* Unsigned division done, now fix up results */ + if (numsign) /* Remainder has same sign as dividend */ + op10m_dneg(qw.D1); /* Negate in place, fixed-point style */ + + /* Now fix up quotient if dividend & divisor had different sign */ + if (numsign != densign) + op10m_dneg(qw.D0); /* Negate in place, fixed-point style */ + + return qw; +} + +/* General Floating-Point arithmetic defs +*/ + +/* PDP-10 Single-precision hardware floating point has the format: +** 1 sign bit +** 8 exponent bits, excess-0200, ones-complement if negative +** 27 fraction bits, twos-complement if negative +** +** PDP-10 Double-precision hardware floating point has the format: +** 1 sign bit +** 8 exponent bits, excess-0200, ones-complement if negative +** 27 fraction bits, twos-complement if negative +** plus 1 ignored low-word sign bit (set 0 by all operations) +** 35 additional fraction bits (62 total) +*/ + + +/* Single-prec Format Exponent macros (also applicable to D format) +** These treat the exponent as a quantity from the LH of high word. +*/ +#define SFEBITS 9 +#define SFEMASK (((h10_t)1<> (H10BITS-SFEBITS)) /* Right-justified sign */ +#define SFEMAGS (SFESIGN-1) /* Right-justified mag bits */ +#define SFELHF (SFEMASK << (H10BITS-SFEBITS)) /* Exponent field in LH */ +#define SFEGET(w) ((LHGET(w) >> (H10BITS-SFEBITS)) & SFEMASK) +#define SFEEXCESS (SFESIGN>>1) /* The N in Excess-N */ + +/* Single-prec Format Fraction macros (also applicable to D format) +** These treat the fraction as a quantity from the LH of high word. +*/ +#define SFFBITS (H10BITS-SFEBITS) /* LH fraction bits */ +#define SFFMASK (((h10_t)1<>SFEBITS) /* Top bit (not a sign) */ +#define SFFMAGS (SFFMASK>>1) /* Remaining mag bits */ + +/* Macro to canonicalize exponent and fraction. +** exp - must be an "int" variable +** w - first word of float/double/gdouble +*/ +#define SFSETUP(exp, w) \ + exp = SFEGET(w); /* Get sign and exponent */\ + if (exp & SFESIGN) { /* Negative? */\ + exp = exp ^ SFEMASK; /* If so, get ones-complement, and */\ + op10m_tlo(w, SFELHF); /* propagate sign thru exp */\ + } else /* Else mask sign+exp out of fract */\ + op10m_tlz(w, SFELHF) + +#define SF_POSSETUP(sign, exp, w) \ + exp = SFEGET(w); /* Get sign and exponent */\ + if (sign = (exp & SFESIGN)) { /* Negative? */\ + exp = exp ^ SFEMASK; /* If so, get ones-complement, and */\ + op10m_tlo(w, SFELHF); /* propagate sign thru exp */\ + op10m_movn(w); /* and make positive */\ + } else /* Else mask sign+exp out of fract */\ + op10m_tlz(w, SFELHF) + +/* After SF_POSSETUP, can test for normalization with: +** op10m_tlnn(w, SFFHIBIT|(SFFHIBIT<<1)) +** If SFFHIBIT<<1 is set, fraction was negative 400,,0. +** Else if SFFHIBIT is set, fraction was either positive +** or a negative less than 400,,0. +** If neither bit is set, fraction is unnormalized. +** Just remember zero is valid if exponent also zero. +*/ + +#define SF_DPOSSETUP(sign, exp, d) \ + exp = SFEGET((d).HI); /* Get sign and exponent */\ + if (sign = (exp & SFESIGN)) { /* Negative? */\ + exp = exp ^ SFEMASK; /* If so, get ones-complement, and */\ + op10m_tlo((d).HI, SFELHF); /* propagate sign thru exp */\ + op10m_dmovn(d); /* and make positive */\ + if (!op10m_tlnn((d).HI, SFFMASK)) /* If carried out of fract */\ + ++exp, LHSET((d).HI, SFFHIBIT); /* adjust it */\ + } else /* Else mask sign+exp out of fract */\ + op10m_tlz((d).HI, SFELHF) + + +#if OP10_GFMT + +/* PDP-10 G Format double-precision floating point has the format: +** 1 sign bit +** 11 exponent bits, excess-02000, ones-complement if negative +** 24 fraction bits, twos-complement if negative +** plus 1 ignored low-word sign bit (set 0 by all operations) +** 35 additional fraction bits (59 total) +** +** Basically identical to Double (D-format) but with 3 bits taken from +** the precision and given to the exponent. +*/ + +/* G Format Exponent macros +** These treat the exponent as a quantity from the LH of high word. +*/ +#define GFEBITS 12 +#define GFEMASK (((h10_t)1<> (H10BITS-GFEBITS)) /* Right-justified sign */ +#define GFEMAGS (GFESIGN-1) /* Right-justified mag bits */ +#define GFELHF (GFEMASK << (H10BITS-GFEBITS)) /* Exponent field in LH */ +#define GFEGET(w) ((LHGET(w) >> (H10BITS-GFEBITS)) & GFEMASK) +#define GFEEXCESS (GFESIGN>>1) /* The N in Excess-N */ + +/* G Format Fraction macros. +** These treat the fraction as a quantity from the LH of high word. +*/ +#define GFFBITS (H10BITS-GFEBITS) /* LH fraction bits */ +#define GFFMASK (((h10_t)1<>GFEBITS) /* Top bit (not a sign) */ +#define GFFMAGS (GFFMASK>>1) /* Remaining mag bits */ + +/* Macro to canonicalize exponent and fraction. +** exp - must be an "int" variable +** w - first word of float/double/gdouble +*/ +#define GFSETUP(exp, w) \ + exp = GFEGET(w); /* Get sign and exponent */\ + if (exp & GFESIGN) { /* Negative? */\ + exp = exp ^ GFEMASK; /* If so, get ones-complement, and */\ + op10m_tlo(w, GFELHF); /* propagate sign thru exp */\ + } else /* Else mask sign+exp out of fract */\ + op10m_tlz(w, GFELHF) + + +/* Macro to insert exponent into high word of G-format double. +*/ +#define GFEXPSET(w, exp) \ + if (wskipl(w)) /* If sign set, use ones-complement of exp */\ + LHSET(w, (LHGET(w) ^ ((h10_t)(exp & GFEMAGS) << (H10BITS-GFEBITS))));\ + else \ + LHSET(w, (LHGET(w) | ((h10_t)(exp & GFEMAGS) << (H10BITS-GFEBITS)))) + +#define GF_DPOSSETUP(sign, exp, d) \ + exp = GFEGET((d).HI); /* Get sign and exponent */\ + if (sign = (exp & GFESIGN)) { /* Negative? */\ + exp = exp ^ GFEMASK; /* If so, get ones-complement, and */\ + op10m_tlo((d).HI, GFELHF); /* propagate sign thru exp */\ + op10m_dmovn(d); /* and make positive */\ + if (!op10m_tlnn((d).HI, GFFMASK)) /* If carried out of fract */\ + ++exp, LHSET((d).HI, GFFHIBIT); /* adjust it */\ + } else /* Else mask sign+exp out of fract */\ + op10m_tlz((d).HI, GFELHF) + + +#endif /* OP10_GFMT */ + +/* Floating-point Single-precision arithmetic */ + +/* Internal flags for ffadr(), selected so most common case (fadr) +** has flags of 0. +*/ +#define FADF_NONORM 04 /* Set to skip normalization */ +#define FADF_NEGB 02 /* Set to negate B before adding */ +#define FADF_NORND 01 /* Set to skip rounding */ + +w10_t op10fadr(register w10_t a, register w10_t b) +{ return ffadr(a, b, 0); /* No negate, Round */ +} + +w10_t op10fad(register w10_t a, register w10_t b) +{ return ffadr(a, b, FADF_NORND); /* No negate, no round */ +} + +w10_t op10fsbr(register w10_t a, register w10_t b) +{ return ffadr(a, b, FADF_NEGB); /* Negate, Round */ +} + +w10_t op10fsb(register w10_t a, register w10_t b) +{ return ffadr(a, b, FADF_NEGB|FADF_NORND); /* Negate, no round */ +} + +/* Common floating add subroutine shared by above external routines. */ + +static w10_t ffadr(register w10_t a, register w10_t b, int flg) +{ + register dw10_t d; + register int i, expa, expb; + register uint18 rbit; /* May be 1<<17 */ + + SFSETUP(expa, a); /* Set up exponent and fraction for A */ + SFSETUP(expb, b); /* Ditto for B */ + + if (flg & FADF_NEGB) /* If actually subtracting B, */ + op10m_movn(b); /* OK to negate fraction now */ + + /* Now see which exponent is smaller; get larger in A, smaller in B. + ** This needs to be done even if one of the args is completely zero, so + ** that the other is set up in A for the normalization code. + */ + if ((i = expa - expb) < 0) { + w10_t tmp; /* Swap args */ + int etmp; + etmp = expb, tmp = b; + expb = expa, b = a; + expa = etmp, a = tmp; + i = -i; + } + + /* Now right-shift B by the number of bits in i, then add to A. */ + d.HI = x_ash(b, -i); /* Shift B and put in hi word */ + op10m_add(d.HI, a); /* then add A to it */ + d.LO = x_ash(b, 35-i); /* Set 2nd word (low of B) */ + + DEBUGPRF(("FADR preN: (%o) %lo,%lo,%lo,%lo\n", expa, DBGV_D(d))); + + /* D now has double-length sum. + ** The high word sign of A is correct; that of low word may not be. + ** Check high 10 bits to determine normalization step. + */ + rbit = 0; + switch (LHGET(d.HI) >> 8) { + case 00004>>2: /* Normalized positive fraction */ + DEBUGPRF(("WINNING POSITIVE\n")); + rbit = (LHGET(d.LO) & (HSIGN>>1)); + break; + + case 00010>>2: /* Overflow of pos fraction */ + case 00014>>2: + DEBUGPRF(("OVERFLOW POSITIVE\n")); + rbit = RHGET(d.HI) & 01; + d.HI = x_ash(d.HI, -1); /* Normalize by shifting right */ + ++expa; /* And adding one to exponent */ + break; + + case 07760>>2: /* Overflow of neg fraction, maybe zero mag */ + case 07764>>2: /* Overflow of neg fraction, nonzero mag */ + DEBUGPRF(("OVERFLOW NEGATIVE\n")); + d = x_ashc(d, -1); /* First normalize into 0777 case, */ + ++expa; /* then fall thru below to check! */ + + case 07770>>2: /* Normalized negative fraction (probably) */ + if (!(flg & FADF_NORND)) { + rbit = LHGET(d.LO) & (HSIGN>>1); + if (rbit && !op10m_txnn(d.LO, (HMASK>>2), HMASK)) + rbit = 0; /* No round if no other bits */ + } + if (op10m_txnn(d.HI, 0777, HMASK) || rbit) { + DEBUGPRF(("WINNING NEGATIVE\n")); + break; + } + DEBUGPRF(("OVERFLOW NEG, ZEROMAG -2^28 case\n")); + ++expa; + op10m_tlo(d.HI, 0400); /* Effect ASHC -1 */ + break; + + /* Anything else is an unnormalized result and must be handled + ** using slow, fully general case. + */ + case 0: + if (!wskipn(d.HI) && !wmagskipn(d.LO)) { + DEBUGPRF(("FADR: S: ZERO RESULT!\n")); + return w10zero; + } + default: + if (flg & FADF_NONORM) /* May not want normalization */ + break; + + /* Must mess with low word, sigh. Ensure it has correct sign bit, + ** then find the first fraction bit in the 2-word sum. + ** Normalization shifts until B0 != B9 OR (B9==1 and B10-35 == 0). + ** The latter case must be checked for if negative. + */ + if (wskipl(d.HI)) { + op10m_signset(d.LO); + if ((i = op10ffo(op10setcm(d.HI))) >= 36) + i = op10ffo(op10setcm(d.LO)) + 35; + } else { + op10m_signclr(d.LO); + if ((i = op10ffo(d.HI)) >= 36) + i = op10ffo(d.LO) + 35; + } + DEBUGPRF(("RENORMALIZATION: %d", i-9)); + /* i now has position of first fraction bit; move it into place. */ + d = x_ashc(d, (i -= 9)); /* Derive shift and do it */ + expa -= i; /* Adjust exponent */ + rbit = LHGET(d.LO) & (HSIGN>>1); /* Find roundup bit */ + if (wskipl(d.HI)) { /* Special checks for neg */ + DEBUGPRF((" checking neg")); + if (!op10m_txnn(d.HI, 0777, HMASK)) { + /* If fraction is all zero, must be the ugly + ** special case of negative 2^28 (777000,,0) + */ + DEBUGPRF((" - special -2^28 case")); + ++expa; + op10m_tlo(d.HI, 0400); /* Effect ASHC -1 */ + rbit = 0; + } else if (rbit && !op10m_txnn(d.LO, (HMASK>>2), HMASK)) { + DEBUGPRF((" - no lower-round bits, rbit=0")); + rbit = 0; /* No round if no other bits */ + } + } + DEBUGPRF(("\n")); + break; + } + + /* At this point, fraction in d.HI is normalized, and rbit is set if a + ** roundup increment must be done (which in rare cases can trigger + ** a single-shift renormalization). + */ + if (!(flg & FADF_NORND) && rbit) { + DEBUGPRF(("ROUNDUP DONE")); + op10m_inc(d.HI); /* Add 1, then check for overflow */ + if (wskipl(d.HI)) { /* Negatives are messy */ + if (LHGET(d.HI) & 0400) { /* If first fract bit is "wrong" */ + if ((LHGET(d.HI) & 0377) || RHGET(d.HI)) { /* See if really OK */ + --expa, d.HI = x_ash(d.HI, 1); /* Nope, shift up */ + DEBUGPRF((" - NEG RENORMALIZED!")); + } + } + } else if (!(LHGET(d.HI) & 0400)) { + ++expa, LHSET(d.HI, 0400); /* Positive overflow, fix up */ + DEBUGPRF((" - POS RENORMALIZED!")); + } + DEBUGPRF(("\n")); + } + DEBUGPRF(("FADR: S: (%o) %lo,%lo\n", expa, DBGV_W(d.HI))); + + /* Ta da! Put exponent back in. We can check now for overflow or + ** underflow. + */ + if (wskipl(d.HI)) /* If sign set, put ones-complement of exp */ + LHSET(d.HI, (LHGET(d.HI) ^ ((h10_t)(expa & 0377) << 9))); + else + LHSET(d.HI, (LHGET(d.HI) | ((h10_t)(expa & 0377) << 9))); + +#if IFFLAGS + if (expa < 0) OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_FXU); + else if (expa > SFEMAGS) OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV); +#endif + return d.HI; +} + + + +w10_t op10fmp(register w10_t a, register w10_t b) +{ return ffmpr(a, b, 0); /* Do without rounding */ +} + +w10_t op10fmpr(register w10_t a, register w10_t b) +{ return ffmpr(a, b, 1); /* Do with rounding */ +} + +static w10_t ffmpr(register w10_t a, register w10_t b, int dornd) +{ + register dw10_t d; + register int sign, exp, i, expb; + + /* Make operands positive and remember sign of result */ + SF_POSSETUP(sign, exp, a); /* Get positive exp and fract */ + + /* Repeat same steps for other arg */ + expb = SFEGET(b); /* Get sign and exponent */ + if (expb & SFESIGN) { /* Negative? */ + sign = !sign; /* Set 0 if both args negative */ + expb = expb ^ SFEMASK; /* If so, get ones-complement */ + op10m_tlo(b, SFELHF); /* propagate sign thru exp */ + op10m_movn(b); /* Then make positive */ + } else + op10m_tlz(b, SFELHF); /* Else mask sign+exp out of fract */ + + /* Add exponents together */ + exp += expb - 0200; + + /* Now multiply the 27-bit magnitude numbers (maybe 28-bit if neg) */ + d = op10xmul(a, b); /* Multiply the numbers, get double result */ + + /* The high 17 bits of product should be empty (1 sign, 16 magnitude bits) + ** If everything was normalized, the next bit (start of fraction) should + ** be 1 and we just shift everything left 8 bits to get in place. + ** If not, must look for first bit of fraction. + */ + DEBUGPRF(("FMP preN: (%d.) %lo,%lo,%lo,%lo\n", exp, DBGV_D(d))); + if (LHGET(d.HI) == 01) i = 8; + else { + /* High fraction bit not set... see if result was 0 */ + if (!wskipn(d.HI) && !wskipn(d.LO)) + return d.HI; /* Zero product, return 0.0 */ + i = adffo(d); /* Find first fract bit in double */ + i -= SFEBITS; /* Adjust for place shifting to */ + } + + /* OK, now normalize. */ + d = x_ashc(d, i); /* Do ASHC bringing in zeros */ + exp -= i - 8; + DEBUGPRF(("FMP pstN: (%d.) %lo,%lo,%lo,%lo\n", exp, DBGV_D(d))); + + /* Now do rounding. See if bit lower than LSB is set; if so, add one + ** to LSB. This might possibly carry all the way to MSB, so be ready + ** to re-normalize. + */ + if (dornd) { + if (op10m_tlnn(d.LO, (HSIGN>>1))) { /* Roundup bit there? */ + op10m_inc(d.HI); + if (op10m_tlnn(d.HI, SFELHF)) { /* If carried out of fract */ + LHSET(d.HI, SFFHIBIT); /* must be 01000, shift it back */ + ++exp; /* and adjust exponent */ + } + } + } + + /* Fraction all done, just add exponent back now */ + op10m_tlo(d.HI, ((h10_t)(exp & SFEMAGS) << SFFBITS)); +#if IFFLAGS + if (exp < 0) OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_FXU); + else if (exp > SFEMAGS) OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV); +#endif + + /* Convert back to negative result if necessary. Note special hackery + ** if not rounding, which merely gives ones-complement if any bits were + ** set in low-order part. This is needed to reproduce hardware, even + ** though it results in a less precise answer! + */ + if (sign) { + if (dornd || !wskipn(d.LO)) + op10m_movn(d.HI); + else + op10m_setcm(d.HI); + } + return d.HI; +} + + +/* FDV - slightly different calling sequence needed because like +** other divides, operation can be aborted without changing +** any operands. +** +** OP10FDV{R} are provided for backward compatibility with optest prog. +*/ + +w10_t op10fdv(w10_t a, w10_t b) +{ + (void) op10xfdv(&a, b, 0); /* Do without rounding */ + return a; /* Return whatever result was */ +} /* (no change if no divide) */ + +w10_t op10fdvr(w10_t a, w10_t b) +{ + (void) op10xfdv(&a, b, 1); /* Do with rounding */ + return a; /* Return whatever result was */ +} /* (no change if no divide) */ + + +int op10xfdv(w10_t *aw, + register w10_t b, + int dornd) +{ + register w10_t a = *aw; + dw10_t d; + register int sign, exp, i, expb; + + /* Make operands positive and remember sign of result */ + + if (sign = wskipl(a)) { /* Negative? */ + op10m_movn(a); /* Then make positive */ + if (wskipl(a)) { /* If numerator is max neg value, */ + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_DIV); + return 0; /* PDP-10 always chokes, sigh */ + } + } + exp = SFEGET(a); /* Get positive exponent */ + op10m_tlz(a, SFELHF); /* Mask sign+exp out of fract */ + + /* Repeat for other arg, but allow max neg number */ + expb = SFEGET(b); /* Get sign and exponent */ + if (expb & SFESIGN) { /* Negative? */ + sign = !sign; /* Set 0 if both args negative */ + expb = expb ^ SFEMASK; /* If so, get ones-complement */ + op10m_tlo(b, SFELHF); /* propagate sign thru exp */ + op10m_movn(b); /* Then make positive */ + } else + op10m_tlz(b, SFELHF); /* Else mask sign+exp out of fract */ + + + /* Subtract exponents. The +1 compensates for pre-multiply of divisor. + */ + exp += 0200 + 1 - expb; + DEBUGPRF(("FDVR (%o) N: %lo,%lo D: %lo,%lo\n", + exp, DBGV_W(a), DBGV_W(b))); + + /* Get two 27-bit (or 28 if SETZ) magnitude numbers and divide them. + ** In order to ensure that the division can succeed, we pre-multiply the + ** divisor by 2. This is compensated for by adjusting the final exponent. + ** This can fail and cause No Divide (etc) if operands aren't normalized. + */ + b = x_ash(b, 1); /* Make divisor be bigger */ + if (op10m_camge(a, b)) { + DEBUGPRF(("FDVR: Fail, N: %lo,%lo >= D: %lo,%lo\n", + DBGV_W(a), DBGV_W(b))); + + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_DIV); + return 0; /* Fail, return original dividend */ + } + + d.HI = a; /* Set up dividend */ + op10m_setz(d.LO); + DEBUGPRF(("FDVR: N: %lo,%lo|%lo,%lo D: %lo,%lo\n", DBGV_D(d), DBGV_W(b))); + + /* Used to be always 30; maybe revert if needed to duplicate + ** unnormalized behavior?? + */ + i = (dornd ? 29 : 28); + if (!ddivstep(&d, b, i, 0)) { /* Divide them, get double result */ + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_DIV); + return 0; /* Fail */ + } + exp += 35-i; /* Adjust for partial division */ + + DEBUGPRF(("FDV preN: Q: %lo,%lo R: %lo,%lo E: %o\n", DBGV_D(d), exp)); + + if (!wskipn(d.HI)) { /* Check for zero result */ + *aw = d.HI; + return TRUE; + } + + /* To compensate for possible unnormalized operands and roundup carry, + ** we do a completely general normalization and round. Note we've already + ** tested for 0 so bits definitely exist. + */ + i = op10ffo(d.HI) - 9; /* Find first one-bit, derive shift */ + if (dornd && i < 0) { /* If right-shifting, */ + /* Note max bits needed are 9, so "int" is big enough */ + register int bit = 1 << (-i-1); /* Find last bit that will vanish */ + if (RHGET(d.HI) & bit) { /* If need to round up, */ + static w10_t wrnd; + LRHSET(wrnd, 0, bit); + op10m_add(d.HI, wrnd); /* do it */ + DEBUGPRF(("ROUNDED UP!\n")); + if (i != (op10ffo(d.HI)-9)) { /* See if carried */ + DEBUGPRF(("CARRIED!\n")); + if (wskipl(d.HI)) { /* Yes, into sign bit? */ + DEBUGPRF(("CARRIED INTO SIGN!\n")); + d.HI = x_ash(d.HI, -1); /* Yes, yecch! Special */ + op10m_signclr(d.HI); /* handling to undo damage */ + --exp; + } else --i; /* Carried but compensation trivial */ + } + } + } + if (i != 8) + DEBUGPRF(("ABNORMALIZATION: %d\n", i)); + + d.HI = x_ash(d.HI, i); /* Shift the fraction into place */ + exp -= i + 8; /* Adjust exponent for shift */ + + DEBUGPRF(("FDV pstN: Q: %lo,%lo R: %lo,%lo E: %o\n", DBGV_D(d), exp)); + + /* Fraction all done, just add exponent back now */ + LHSET(d.HI, (LHGET(d.HI) | ((h10_t)(exp & SFEMAGS) << SFFBITS))); +#if IFFLAGS + if (exp < 0) OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_FXU); + else if (exp > SFEMAGS) OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV); +#endif + + /* Ugly hackery - see PRM 2-23 "Note" for FDV. + ** For FDV on KL+KS a negative Q is represented by twos-complement only if + ** remainder is 0, else ones-complement (similar to the way FMP works!). + ** On KA+KI it is always twos-complement... + */ + if (sign) { +#if OP10_FDV_KL /* KL or KS */ + if (!dornd) { + /* Fix up remainder for testing */ + if (wskipl(d.LO)) /* If negative, */ + op10m_add(d.LO, b); /* add divisor back in */ + if (wskipn(d.LO)) + op10m_setcm(d.HI); + else + op10m_movn(d.HI); + } else +#endif + op10m_movn(d.HI); + } + *aw = d.HI; + return TRUE; +} + +#if 0 /* Old version */ + +static w10_t ffdvr(a, b, dornd) +register w10_t a, b; +{ + dw10_t d; + register int sign, exp, i, expb; + + /* Make operands positive and remember sign of result */ + d.HI = a; /* Preserve A in case fail */ + if (sign = wskipl(d.HI)) { /* Negative? */ + op10m_movn(d.HI); /* Then make positive */ + if (wskipl(d.HI)) { /* If numerator is max neg value, */ + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_DIV); + return a; /* PDP-10 always chokes, sigh */ + } + } + exp = SFEGET(d.HI); /* Get positive exponent */ + op10m_tlz(d.HI, SFELHF); /* Mask sign+exp out of fract */ + + /* Repeat for other arg, but allow max neg number */ + expb = SFEGET(b); /* Get sign and exponent */ + if (expb & SFESIGN) { /* Negative? */ + sign = !sign; /* Set 0 if both args negative */ + expb = expb ^ SFEMASK; /* If so, get ones-complement */ + op10m_tlo(b, SFELHF); /* propagate sign thru exp */ + op10m_movn(b); /* Then make positive */ + } else + op10m_tlz(b, SFELHF); /* Else mask sign+exp out of fract */ + + + /* Subtract exponents. The +1 compensates for pre-multiply of divisor. + */ + exp += 0200 + 1 - expb; + DEBUGPRF(("FDVR (%o) N: %lo,%lo D: %lo,%lo\n", + exp, DBGV_W(d.HI), DBGV_W(b))); + + /* Get two 27-bit (or 28 if SETZ) magnitude numbers and divide them. + ** In order to ensure that the division can succeed, we pre-multiply the + ** divisor by 2. This is compensated for by adjusting the final exponent. + ** This can fail and cause No Divide (etc) if operands aren't normalized. + */ + b = x_ash(b, 1); /* Make divisor be bigger */ + if (op10m_camge(d.HI, b)) { + DEBUGPRF(("FDVR: Fail, N: %lo,%lo >= D: %lo,%lo\n", + DBGV_W(d.HI), DBGV_W(b))); + + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_DIV); + return a; /* Fail, return original dividend */ + } + + op10m_setz(d.LO); /* Set up dividend */ + DEBUGPRF(("FDVR: N: %lo,%lo|%lo,%lo D: %lo,%lo\n", DBGV_D(d), DBGV_W(b))); + + if (!ddivstep(&d, b, 30, 0)) { /* Divide them, get double result */ + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_DIV); + return a; + } + exp += 35-30; /* Adjust for partial division */ + + DEBUGPRF(("FDVR: Q: %lo,%lo R: %lo,%lo E: %o\n", DBGV_D(d), exp)); + + if (!wskipn(d.HI)) /* Check for zero result */ + return d.HI; + + /* To compensate for possible unnormalized operands and roundup carry, + ** we do a completely general normalization and round. Note we've already + ** tested for 0 so bits definitely exist. + */ + i = op10ffo(d.HI) - 9; /* Find first one-bit, derive shift */ + if (dornd && i < 0) { /* If right-shifting, */ + /* Note max bits needed are 9, so "int" is big enough */ + register int bit = 1 << (-i-1); /* Find last bit that will vanish */ + if (RHGET(d.HI) & bit) { /* If need to round up, */ + static w10_t wrnd; + LRHSET(wrnd, 0, bit); + op10m_add(d.HI, wrnd); /* do it */ + DEBUGPRF(("ROUNDED UP!\n")); + if (i != (op10ffo(d.HI)-9)) { /* See if carried */ + DEBUGPRF(("CARRIED!\n")); + if (wskipl(d.HI)) { /* Yes, into sign bit? */ + DEBUGPRF(("CARRIED INTO SIGN!\n")); + d.HI = x_ash(d.HI, -1); /* Yes, yecch! Special */ + op10m_signclr(d.HI); /* handling to undo damage */ + --exp; + } else --i; /* Carried but compensation trivial */ + } + } + } + if (i != 8) + DEBUGPRF(("ABNORMALIZATION: %d\n", i)); + + d.HI = x_ash(d.HI, i); /* Shift the fraction into place */ + exp -= i + 8; /* Adjust exponent for shift */ + + /* Fraction all done, just add exponent back now */ + LHSET(d.HI, (LHGET(d.HI) | ((h10_t)(exp & SFEMAGS) << SFFBITS))); +#if IFFLAGS + if (exp < 0) OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_FXU); + else if (exp > SFEMAGS) OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV); +#endif + + if (sign) + op10m_movn(d.HI); + return d.HI; +} + +#endif /* 0 - old version */ + +/* Single-precision Floating-point Conversions */ + +/* SFNORM - Returns normalized single-precision floating point, given +** full-word integer fraction and exponent to use if fraction were +** already in proper position (first significant bit at B9). +** Sets flags if exponent overflows. +*/ +static w10_t sfnorm(register int exp, + register w10_t w, /* Contains signed integer fraction */ + int rnd) /* TRUE to round result */ +{ + register int i, rbit; /* rbit only needs 9 bits, so "int" safe */ + + /* Normalizing a negative number has a lot of gross hair associated + ** with it. By far the simplest thing to do is convert all negative + ** numbers to positive values and only negate back at the end of + ** normalization. + ** Skeptical? See the old code here, included for posterity. Good luck. + */ +#if 0 /* Old code */ +# define bmask(n) (((h10_t)1<<(n))-1) /* Mask of N right-justified bits */ + /* Alternative def: + define bmask(n) (01777777>>(n)) + Then refs change from + bmask(18-i) => bmask(i+1) + bmask(36-i) => bmask(i-17) + Only used in sfnorm()... + */ + if (wskipl(w)) { /* Find 1st significant bit */ + i = op10ffo(op10setcm(w)); + /* Messy - must see if any lower bits are set, and if not, + ** adjust position back to compensate for 2s-complement form. + */ + if (i < 17) { /* Check LH and RH both? */ + if (!RHGET(w) && !(LHGET(w) & bmask(18-i))) --i; + } else if (i < 36) { /* Check only RH */ + if (!(RHGET(w) & bmask(36-i))) --i; + } else i = 35; /* w is -1 */ + + } else i = op10ffo(w); + DEBUGPRF(("SFNORM: %lo,%lo i=%d\n", DBGV_W(w), i)); + if (i >= 36) + return w10zero; + + /* i now has position of first fraction bit; move it into place. + ** For a negative number, the roundup bit is 1 only if at least + ** one lower bit is set. + */ + i -= 9; /* Make relative to proper position, B9 */ + if (rnd && i < 0) { /* If rounding OK and losing bits */ + rbit = 1 << ((-i)-1); /* Get bitmask for last bit lost */ + if (wskipl(w)) /* If negative, must have at least one 1-bit */ + rbit = RHGET(w) & (rbit-1); /* in bits lost off right */ + else rbit = RHGET(w) & rbit; + } else rbit = 0; + if (i) + w = x_ash(w, i); /* Derive shift and do it */ + exp -= i; /* Adjust exponent */ + + /* At this point, number in is normalized, and rbit is set if a + ** roundup increment must be done (which in rare cases can trigger + ** a single-shift renormalization). + */ + if (rbit) { + DEBUGPRF(("ROUNDUP DONE")); + op10m_inc(w); /* Add then check for overflow */ + if (wskipl(w)) { /* Negatives are messy */ + if (LHGET(w) & 0400) { /* If first fract bit is "wrong" */ + if ((LHGET(w) & 0377) || RHGET(w)) { /* See if really OK */ + --exp, w = x_ash(w, 1); /* Nope, shift up */ + DEBUGPRF((" - NEG RENORMALIZED!")); + } + } + } else if (!(LHGET(w) & 0400)) { + ++exp, LHSET(w, 0400); /* Positive overflow, fix up */ + DEBUGPRF((" - POS RENORMALIZED!")); + } + DEBUGPRF(("\n")); + } + + /* Ta da! Put exponent back in. We can check now for overflow or + ** underflow. + */ + if (wskipl(w)) /* If sign set, put ones-complement of exp */ + LHSET(w, (LHGET(w) ^ ((h10_t)(exp & 0377) << 9))); + else + LHSET(w, (LHGET(w) | ((h10_t)(exp & 0377) << 9))); + +#else /* End old code, start new code */ + + /* New regime. Check for negative fraction and convert to use + ** positive value instead. The fraction is always assumed to be signed. + ** + ** Note that the maximum negative number cannot be made positive, + ** but this is compensated for by remembering the sign and using + ** unsigned operations. + */ + register int sign; + + if (sign = wskipl(w)) + op10m_movn(w); /* Negate the fraction, max neg is OK. */ + i = op10ffo(w); + + DEBUGPRF(("SFNORM: %lo,%lo i=%d exp=%o\n", DBGV_W(w), i, exp)); + + if (i >= 36) + return w10zero; /* No magnitude bits, so just return 0 */ + + /* i now has position of first fraction bit; move it into place. + */ + i -= 9; /* Make relative to proper position, B9 */ + exp -= i; /* Adjust exponent */ + if (rnd && i < 0) /* If rounding OK and losing bits */ + rbit = RHGET(w) /* Find value of last bit that will be lost */ + & (1 << ((-i)-1)); + else rbit = 0; + + if (i < 0) /* Now do logical (unsigned) shift! */ + RSHIFTM(w, -i); + else if (i > 0) + LSHIFTM(w, i); + + DEBUGPRF(("SFNORMED: %lo,%lo i=%d exp=%o rbit=%o\n", + DBGV_W(w), i, exp, rbit)); + + /* At this point, number in w is normalized, and rbit is set if a + ** roundup increment must be done. In the rare case that this + ** increment overflows, its value will be 1000,,0 and will trigger + ** a single-shift renormalization. + */ + if (rbit) { + op10m_inc(w); /* Add, then check for overflow */ + if (LHGET(w) > SFFMASK) { + ++exp; /* Positive overflow, fix up */ + LHSET(w, 0400); + DEBUGPRF(("ROUNDUP - RENORMED!\n")); + } else + DEBUGPRF(("ROUNDUP\n")); + } + + /* Fraction is normalized with exponent bits clear, can just OR + ** exponent back in, and negate if needed to finish off result. + */ + op10m_tlo(w, ((h10_t)(exp & SFEMAGS) << SFFBITS)); + if (sign) + op10m_movn(w); + + /* Ta da! Check before returning for overflow or underflow. + ** Note that the negation, if one, can never cause either since a + ** positive number can always be negated. + */ +#endif /* New code */ + +#if IFFLAGS + if (exp < 0) OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_FXU); + else if (exp > SFEMAGS) OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV); +#endif + + return w; +} + +w10_t +op10fltr(register w10_t w) +{ + return sfnorm(SFEEXCESS+27, w, 1); /* Normalize and round */ +} + +/* FSC +** Testing weirdness: when AC has a single-bit fraction (either +** positive or complemented) and E is 400000, then *only* Floating +** Underflow is set on a real DEC-2065. The proc ref man claims +** Overflow and Floating Overflow should always also be set, and +** that's what this code does. +*/ +w10_t +op10fsc(register w10_t w, register h10_t h) +{ + register int exp; + + SFSETUP(exp, w); /* Set up exponent and fraction */ + exp += imm8op(h); /* Scale per immediate arg */ + return sfnorm(exp, w, 0); /* Put float together, don't round */ +} + +w10_t op10fix(w10_t w) +{ + op10xfix(&w, 0); /* Fix, no round */ + return w; +} + +w10_t op10fixr(w10_t w) +{ + op10xfix(&w, 1); /* Fix, with round */ + return w; +} + +/* Single Float->Fix conversion. +** Note that rounding does NOT work the same way as for floats. +** See PRM 2-28. +** +(1.5) is rounded to +2, but -(1.5) is rounded to -1! +** +** If the first bit of the fraction is 1 for a positive number, +** then 1 is added to the integral part (thus increasing its magnitude). +** If the first bit of the fraction is 1 for a negative number, +** then 1 is added to the integral part (thus lowering its magnitude). +** +** This produces asymmetric results due to twos-complement nature. +** Apparently this was done for simplicity or to follow the "Algol +** standard". +*/ +int op10xfix(w10_t *aw, int dornd) +{ + register w10_t w = *aw; + +#if 1 + register int i, sign; + register uint18 rbit; + + SF_POSSETUP(sign, i, w); /* Set up exponent, get positive fract */ + + /* Apply exponent size test now */ + if (i > (SFEEXCESS+35)) { /* If integer wd be too big, */ + OP10_PCFSET(PCF_ARO+PCF_TR1); /* set flags */ + return 0; /* and return arg unchanged */ + } + + /* OK to proceed! + ** Fraction should now be a 27-bit positive integer. It will be a 28-bit + ** value of 1000,,0 only if given an unnormalized negative float with + ** no one-bits in the fraction part. + */ + i -= (SFEEXCESS+27); /* Get # bits to shift fraction */ + if (i <= -28) { /* If shifting it to oblivion */ + *aw = w10zero; /* just return zero, skip rigmarole */ + return 1; + } + + /* Determine whether to round. + ** Note special test for negative values, needed in order to + ** emulate peculiar way FIXR does rounding. + */ + if (dornd && i < 0) { /* If rounding OK and losing bits */ + if (i < -H10BITS) { /* Find value of last bit that will be lost */ + rbit = LHGET(w) /* Bit comes from LH */ + & (1 << ((-i)-(H10BITS+1))); + } else { + rbit = RHGET(w) /* Bit comes from RH */ + & (((uint18)1) << ((-i)-1)); + } + if (rbit && sign /* Special test for negative .5 */ + && !op10m_tdnn(w, w10loobits[(-i)-1])) + rbit = 0; /* No round if so */ + } else rbit = 0; + + if (i < 0) { /* Now do logical (unsigned) shift! */ + RSHIFTM(w, -i); + if (rbit) /* Need any rounding? */ + op10m_inc(w); /* Yep, trivial */ + } else if (i > 0) + LSHIFTM(w, i); + + if (sign) /* Now negate back if necessary */ + op10m_movn(w); + +#else /* New regime! */ + + register int i; + register uint18 rbit; + + SFSETUP(i, w); /* Set up exponent, get signed fract */ + + /* Apply exponent size test now */ + if (i > (SFEEXCESS+35)) { /* If integer wd be too big, */ + OP10_PCFSET(PCF_ARO+PCF_TR1); /* set flags */ + return 0; /* and return arg unchanged */ + } + + /* OK to proceed! + ** Fraction should now be a 27-bit positive or negative integer. + ** It will be a 28-bit value of -1000,,0 only if given an unnormalized + ** negative float with no one-bits in the fraction part. + */ + i -= (SFEEXCESS+27); /* Get # bits to shift fraction */ + if (i <= -28) { /* If shifting it to oblivion */ + *aw = w10zero; /* just return zero, skip rigmarole */ + return 1; + } + + /* Determine whether to round. + */ + if (dornd && i < 0) { /* If rounding OK and losing bits */ + if (i < -H10BITS) { /* Find value of last bit that will be lost */ + rbit = LHGET(w) /* Bit comes from LH */ + & (1 << ((-i)-(H10BITS+1))); + } else { + rbit = RHGET(w) /* Bit comes from RH */ + & (((uint18)1) << ((-i)-1)); + } + } else rbit = 0; + + if (i < 0) { /* Now shift! */ + if (wskipl(w)) + NASH_RSHIFTM(w, -i); /* Negative, must do ASH */ + else + RSHIFTM(w, -i); /* Positive, fast logical is OK */ + if (rbit) /* Need any rounding? */ + op10m_inc(w); /* Yep, trivial */ + } else if (i > 0) + LSHIFTM(w, i); /* Left-shift can always be logical */ +#endif + + *aw = w; /* Return result */ + return 1; /* Say succeeded */ +} + +/* Floating-point Double-precision arithmetic. REAL moby hair! */ + + +dw10_t op10dfad(dw10_t a, dw10_t b) +{ + return dfad(a, b, 0); +} + +dw10_t op10dfsb(dw10_t a, dw10_t b) +{ + return dfad(a, b, 1); +} + +/* Common DFAD/DFSB routine. + + The PRM says addition is done in a "triple-length" sum. It's +unclear exactly how many magnitude bits this is. + For KLH10_CPU_KLX at least, 3 words of 27+35+35 bits are not enough; +the following computation failed when using just one extra word for +the "lost" roundup bits, because the low bit of the 2nd operand was +being shifted out of the 3rd word. Even though these operands are +not normalized, ideally the emulation should still produce the +same results. + +2065: dfad 700000,0,0,0 776000,0,0,1 => 677400,0,0,0 +Emul: dfad 700000,0,0,0 776000,0,0,1 => 677377,777777,377777,777777 + + So, for the time being this code uses 4 words, with the lowest +word used only to hold shifted-out bits for possible rounding computations. + +*/ + +static dw10_t dfad(register dw10_t a, + register dw10_t b, + int negb) +{ + register int i, expa, expb; + register uint18 rbit; /* May be 1<<17 */ + register dw10_t rd; + + SFSETUP(expa, a.HI); /* Set up exponent and fraction */ + SFSETUP(expb, b.HI); /* Ditto for other arg */ + + if (negb) /* If actually subtracting B, */ + op10m_dmovn(b); /* negate double fraction now */ + + /* Now see which exponent is smaller; get larger in A, smaller in B. + ** This needs to be done even if one of the args is completely zero, so + ** that the other is set up in A for the normalization code. + */ + if ((i = expa - expb) < 0) { + dw10_t tmp; /* Swap args */ + int etmp; + etmp = expb, tmp = b; + expb = expa, b = a; + expa = etmp, a = tmp; + i = -i; + } + DEBUGPRF(("DFAD A: (%o) %lo,%lo,%lo,%lo B: (%o) %lo,%lo,%lo,%lo i=%d\n", + expa, DBGV_D(a), expb, DBGV_D(b), i)); + + /* Now right-shift B by the number of bits in i, then add to A. + */ + rd = x_ashc(b, 70-i); /* Get bottom 2 words of a 4-word ASHC */ + b = x_ashc(b, -i); /* Shift B for adding */ + DEBUGPRF(("DFAD B: (%o) %lo,%lo,%lo,%lo R: %lo,%lo,%lo,%lo\n", + expa, DBGV_D(b), DBGV_D(rd))); + + a = x_dadd(a, b); /* Add into A */ + + DEBUGPRF((" preN: (%o) %lo,%lo,%lo,%lo R: %lo,%lo,%lo,%lo\n", + expa, DBGV_D(a), DBGV_D(rd))); + + /* A now has sum, with the low-order 3rd word in RD's high word. + ** The sign of A is correct; that of RD is irrelevant. + ** Check high 10 bits to determine normalization step. + */ + rbit = 0; + switch (LHGET(a.HI) >> 8) { + case 00004>>2: /* Normalized positive fraction */ + DEBUGPRF(("WINNING POSITIVE\n")); + rbit = (LHGET(rd.HI) & (HSIGN>>1)); + break; + + case 00010>>2: /* Overflow of pos fraction */ + case 00014>>2: + DEBUGPRF(("OVERFLOW POSITIVE\n")); + rbit = RHGET(a.LO) & 01; + a = x_ashc(a, -1); /* Normalize by shifting right */ + ++expa; /* And adding one to exponent */ + break; + + case 07760>>2: /* Overflow of neg fraction, maybe zero mag */ + case 07764>>2: /* Overflow of neg fraction, nonzero mag */ + DEBUGPRF(("OVERFLOW NEGATIVE\n")); + rbit = RHGET(a.LO) & 01; + a = x_ashc(a, -1); /* First normalize into 0777 case, */ + rd = x_ashc(rd, -1); /* Shift RD as well, */ + if (rbit) /* Copy bit lost from A */ + LHSET(rd.HI, (LHGET(rd.HI) | (HSIGN>>1))); + else + LHSET(rd.HI, (LHGET(rd.HI) & ~(HSIGN>>1))); + ++expa; /* then fall thru below to check! */ + + case 07770>>2: /* Normalized negative fraction (probably) */ + rbit = LHGET(rd.HI) & (HSIGN>>1); + DEBUGPRF(("DFAD:NEG: (%o) %lo,%lo,%lo,%lo %lo,%lo,%lo,%lo r=%lo\n", + expa, DBGV_D(a), DBGV_D(rd), (long)rbit )); + + if (!(LHGET(rd.HI)&(HMASK>>2)) && !RHGET(rd.HI) + && !wmagskipn(rd.LO)) { + rbit = 0; /* No round if no other bits */ + DEBUGPRF(("NEG - RND DROPPED - ")); + } + if ((LHGET(a.HI)&0777) || RHGET(a.HI) || rbit || wmagskipn(a.LO)) { + DEBUGPRF(("WINNING NEGATIVE\n")); + break; + } + DEBUGPRF(("OVERFLOW NEG (ZERO MAG)\n")); + rbit = 0; + LHSET(a.HI, 0777400); /* Pretend right-shifted 1 bit */ + ++expa; + break; + + /* Anything else is an unnormalized result and must be handled + ** using fully general case. + */ + case 0: + if (!wskipn(a.HI) && !wmagskipn(a.LO) && !wmagskipn(rd.HI)) { + DEBUGPRF(("DFAD: S: ZERO RESULT!\n")); + return dw10zero; + } + default: + /* Must mess with RD, sigh. Ensure it has correct sign bit, + ** then find the first fraction bit in the 3-word sum. + */ + b.LO = rd.HI; + if (wskipl(a.HI)) { + op10m_signset(b.LO); + if ((i = op10ffo(op10setcm(a.HI))) >= 36) { + if ((i = op10ffo(op10setcm(a.LO))) >= 36) { + i = op10ffo(op10setcm(b.LO)) + 70; + } else i += 35; + } + } else { + op10m_signclr(b.LO); /* Clear sign bit */ + if ((i = op10ffo(a.HI)) >= 36) { + if ((i = op10ffo(a.LO)) >= 36) { + i = op10ffo(b.LO) + 70; + } else i += 35; + } + } + DEBUGPRF(("RENORMALIZATION: first %c-bit at %d = ASHC %d\n", + (wskipl(a.HI) ? '0' : '1'), i, i-9)); + + /* i now has position of first fraction bit; move it into place. */ + b.HI = a.LO; /* Duplicate middle word for easy shift */ + if ((i -= 9) < 35) { /* Right shift or small left shift */ + a = x_ashc(a, i); + b = x_ashc(b, i); + if (i > 0) a.LO = b.HI; + } else if (i < 70) { /* One-word or larger shift */ + a = x_ashc(b, i-35); + op10m_setz(b.LO); + } else { /* Two-word or larger shift */ + a.HI = x_ash(b.LO, i-70); + op10m_setz(a.LO); + op10m_setz(b.LO); + } + DEBUGPRF(("DFAD: S1:(%o) %lo,%lo,%lo,%lo R: %lo,%lo\n", + expa-i, DBGV_D(a), + (long)LHGET(b.LO), (long)RHGET(b.LO))); + + /* Now find roundup bit, and do fixups for negative results */ + rbit = LHGET(b.LO) & (HSIGN>>1); /* Find roundup bit */ + if (wskipl(a.HI)) { /* Special checks for neg */ + if (!(LHGET(b.LO)&(HMASK>>2)) && !RHGET(b.LO)) + rbit = 0; /* No round if no other bits */ + if (LHGET(a.HI) == 0777000 && !RHGET(a.HI) && !wmagskipn(a.LO) + && !rbit) { + ++expa, a = x_ashc(a, -1); /* Make LH 0777400 */ + DEBUGPRF((" NEG FIXUP\n")); + } + } + expa -= i; /* Adjust exponent */ + break; + } + + /* At this point, number in A is normalized, and rbit is set if a + ** roundup increment must be done (which in rare cases can trigger + ** a single-shift renormalization). + */ + if (rbit) { + DEBUGPRF(("ROUNDUP DONE")); + a = op10dfinc(a); /* Add then check for overflow */ + if (wskipl(a.HI)) { /* Negatives are messy */ + if (LHGET(a.HI) & 0400) { /* If first fract bit is "wrong" */ + if ((LHGET(a.HI) & 0377) || RHGET(a.HI) /* See if really OK */ + || wmagskipn(a.LO)) { + --expa, a = x_ashc(a, 1); /* Nope, shift up */ + DEBUGPRF((" - NEG RENORMALIZED!")); + } + } + } else if (!(LHGET(a.HI) & 0400)) { + ++expa, LHSET(a.HI, 0400); /* Positive overflow, fix up */ + DEBUGPRF((" - POS RENORMALIZED!")); + } + DEBUGPRF(("\n")); + } + DEBUGPRF(("DFAD: S: (%o) %lo,%lo,%lo,%lo\n", expa, DBGV_D(a))); + + /* Ta da! Put exponent back in (it may have underflowed or overflowed + ** but nothing we can do about it). + */ + if (wskipl(a.HI)) /* If sign set, put ones-complement of exp */ + LHSET(a.HI, (LHGET(a.HI) ^ ((h10_t)(expa & SFEMAGS) << 9))); + else + LHSET(a.HI, (LHGET(a.HI) | ((h10_t)(expa & SFEMAGS) << 9))); +#if IFFLAGS + if (expa < 0) OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_FXU); + else if (expa > SFEMAGS) OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV); +#endif + op10m_signclr(a.LO); /* Double flt always zeros low sign */ + return a; +} + + +dw10_t op10dfmp(register dw10_t a, register dw10_t b) +{ + register qw10_t q; + register int exp, expb; + register int sign; + int signb; + + /* Make operands positive and remember sign of result + ** These macros guarantee a positive number of at most 62 magnitude bits, + ** even for the max-neg-# case. + */ + SF_DPOSSETUP(sign, exp, a); + SF_DPOSSETUP(signb, expb, b); + + DEBUGPRF(("DFMP: A: (%o) %lo,%lo,%lo,%lo B: (%o) %lo,%lo,%lo,%lo\n", + exp, DBGV_D(a), expb, DBGV_D(b))); + + + sign ^= signb; /* Set 0 if both were negative */ + exp += expb - SFEEXCESS; /* Find resulting exponent */ + + /* Have two 62-bit magnitude numbers, multiply them (124-bit product) */ + + q = x_dmul(a, b); /* Multiply the numbers, get quad result */ + + DEBUGPRF(("DFMP Q: %lo,%lo,%lo,%lo|%lo,%lo,%lo,%lo exp=%o (%d.)\n", + DBGV_Q(q), exp, exp)); + + /* Since there are 124 bits of product with 35 magnitude bits in each + ** word, the high word should contain 124-105 = 19 high mag bits. + ** Thus the top 36-19=17 bits of product should be empty. + ** If everything was normalized, the next bit (start of fraction, B17) + ** should be 1 and we just shift everything left by 17-9=8 bits to get + ** in place. + ** If not, must normalize by looking for first bit of fraction. + */ + if (!op10m_tlnn(q.D0.HI, 1<<(H10BITS-(1+17)))) { /* Test B17 */ + /* No high fraction bits set... see if result was 0 */ + if (!RHGET(q.D0.HI) && !wskipn(q.D0.LO) && !dskipn(q.D1)) + return q.D0; /* Zero product, return 0.0 */ + do { + q = x_qash1(q); /* ASH quad up one bit */ + --exp; /* Adjust exponent accordingly */ + } while (!op10m_tlnn(q.D0.HI, 1<<(H10BITS-(1+17)))); + /* Until get high fract bit */ + } + + /* Fraction in expected place, ASH 17-9=8 bits left to align for exp */ + q.D0 = x_ashc(q.D0, 8); /* Do ASHC bringing in 8 zeros */ + op10m_iori(q.D0.LO, /* Add 8 bits from 3rd wd */ + (LHGET(q.D1.HI) >> ((HBITS-1)-8))); + + DEBUGPRF(("DFMP2Q: %lo,%lo,%lo,%lo|%lo,%lo,%lo,%lo exp=%o (%d.)\n", + DBGV_Q(q), exp, exp)); + + /* Now do rounding. See if bit lower than LSB is set (this is the next + ** bit after the ones added from 3rd word). + ** If so, add one to LSB. This will overflow only if the fraction was + ** all ones, resulting in a roundup to all zeros except the low exponent + ** bit. If so, re-normalize by simply resetting the LH to right value + ** and adjusting the exponent. + */ + if (LHGET(q.D1.HI) & (1 << ((HBITS-1)-(8+1))) ) { + /* Special hack in attempt to emulate hardware. Don't round if + ** result is going to be negative and there are no more low bits + ** set in the product (to the right of the round bit). + */ + if (sign && !op10m_tlnn(q.D1.HI, 0377) + && !RHGET(q.D1.HI) && !wskipn(q.D1.LO)) { + DEBUGPRF(("DFMP ROUNDUP NEGSKIPPED\n")); + } else { + DEBUGPRF(("DFMP ROUNDUP\n")); + q.D0 = op10dfinc(q.D0); + if (op10m_tlnn(q.D0.HI, SFELHF)) { /* If carried out of fract */ + LHSET(q.D0.HI, SFFHIBIT); /* reset to proper value */ + ++exp; /* and adjust exponent */ + DEBUGPRF(("DFMP POS RENORM\n")); + } + } + } + + /* Fraction all done, just add exponent back now */ + LHSET(q.D0.HI, (LHGET(q.D0.HI) | ((h10_t)(exp & SFEMAGS) << SFFBITS))); +#if IFFLAGS + if (exp < 0) OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_FXU); + else if (exp > SFEMAGS) OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV); +#endif + + if (sign) + op10m_dmovn(q.D0); + return q.D0; +} + +/* DFDV - note no-divide case simply returns 1st arg instead of +** failing explicitly. +*/ + +dw10_t op10dfdv(register dw10_t a, register dw10_t b) +{ + qw10_t q; + register int exp, i; + register int expb, sign, signb; + +#if 1 /* New code */ + /* Make operands positive and remember sign of result */ + /* The KLX appears to have a peculiarly rigid filter for the + ** args of DFDV which the other DF ops don't share. + ** If the 2nd arg (divisor) is 0, result is always no-divide. + ** else, if the 1st arg is 400000,,0 0,,0 + ** then the result is a No-Divide (regardless of 2nd arg). + ** else, if the 1st arg has no fraction bits set, + ** then the result is always 0, even though this could be + ** an unnormalized value. This test is NOT applied + ** to the divisor!!! + */ + SF_DPOSSETUP(signb, expb, b); /* Set up 2nd arg normally */ + if (!wskipn(b.HI) && !wmagskipn(b.LO)) { /* Check for zero divisor */ + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_DIV); + return a; /* Ugh, No Divide error. Return original op */ + } + + /* Now set up A. Note a different mechanism is used in order to emulate + ** the way that the KL apparently chops off all exponent bits when + ** extracting the fraction bits, thus turning an unnormalized negative + ** number of (e.g.) 777000,,0 into 0,,0 instead of 1000,,0. + **/ + q.D0 = a; /* Set up A in Q (preserves orig value) */ + if (wskipl(q.D0.HI)) { + op10m_dmovn(q.D0); + if (wskipl(q.D0.HI)) { + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_DIV); + return a; /* Ugh, No Divide error. Return original op */ + } + sign = !signb; + } else sign = signb; + exp = SFEGET(q.D0.HI); /* Get sign and exponent */ + op10m_tlz(q.D0.HI, SFELHF); /* Chop out exponent bits */ + if (!wskipn(q.D0.HI) && !wmagskipn(q.D0.LO)) + return dw10zero; /* No fraction bits, return 0 */ + + DEBUGPRF(("DFDV: A: (%o) %lo,%lo,%lo,%lo B: (%o) %lo,%lo,%lo,%lo\n", + exp, DBGV_D(q.D0), expb, DBGV_D(b))); + + exp += SFEEXCESS - expb; /* Do division on exponents */ + + /* Now guaranteed to have two positive 62-bit magnitude numbers. */ + + +#else /* end new, start old */ + + /* Make operands positive and remember sign of result */ + q.D0 = a; + if (sign = wskipl(a.HI)) { + op10m_dmovn(q.D0); + + /* KLX always fails immediately with No Divide if dividend is + ** 400000,,0 ? 0,,0 + */ + if (wskipl(a.HI)) { + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_DIV); + return a; /* Ugh, No Divide error. Return original op */ + } + } + if (wskipl(b.HI)) { + op10m_dmovn(b); + sign = !sign; /* Invert sense of sign flag */ + } + + /* Subtract exponents. + */ + exp = 0200 + (LHGET(q.D0.HI) >> 9) - (LHGET(b.HI) >> 9); + + /* Get two 62-bit magnitude numbers and divide them. + ** In order to ensure that the division can succeed, we pre-multiply the + ** divisor by 2. This is compensated for by adjusting the final exponent. + ** This can fail with No Divide if the operands aren't normalized. + */ + /* Mask out everything but fraction */ + LHSET(q.D0.HI, (LHGET(q.D0.HI) & 0777)); + LHSET(b.HI, (LHGET(b.HI) & 0777)); +#endif /* Old code */ + + /* Divide them. + ** Note that floating fractions, if normalized, will always have their most + ** significant bit in the same place, which confuses the ordinary division + ** test. + ** So, in order to ensure that the division can succeed, we pre-multiply + ** the divisor by 2 (to make it a 63-bit value). + ** This is compensated for by adjusting the final exponent. + ** This can fail with No Divide if the operands aren't normalized. + ** + ** Note that the udcmpl test relies on both args being positive and the + ** low sign bit being clear. + */ + op10m_signclr(q.D0.LO); /* Ensure valid for udcmpl test */ + op10m_signclr(b.LO); + if (!op10m_udcmpl(q.D0, b)) { /* If q.D0 >= b */ + DEBUGPRF(("DFDV Divisor pre-multiply!\n")); + b = x_ashc(b, 1); /* Make divisor be bigger */ + ++exp; + if (!op10m_udcmpl(q.D0, b)) { /* If q.D0 >= b */ + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_DIV); + return a; /* Ugh, No Divide error. Return original op */ + } + } + q.D1 = dw10zero; /* Set up dividend */ +#if 0 + DEBUGPRF(("DFDV: N: %lo,%lo,%lo,%lo|%lo,%lo,%lo,%lo D: %lo,%lo,%lo,%lo\n", + DBGV_Q(q), DBGV_D(b))); +#endif + + if (!qdivstep(&q, b, 63)) { + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_DIV); + return a; + } + exp += 70-63; /* Compensate for dividing only 63 steps */ + + DEBUGPRF(("DFDV: Q: %lo,%lo,%lo,%lo R: %lo,%lo,%lo,%lo E: %o\n", + DBGV_Q(q), exp)); + + if (!dskipn(q.D0)) /* Check for zero result */ + return q.D0; + + /* To compensate for possible unnormalized operands and roundup carry, + ** we do a completely general normalization and round. Note we've already + ** tested for 0 so bits definitely exist. + */ + i = adffo(q.D0) - 9; /* Find first one-bit, derive shift */ + if (i < 0) { /* If right-shifting, */ + /* bit only needs 9 bits, so "int" is safe */ + register int bit = 1 << (-i-1); /* Find last bit that will vanish */ + if (RHGET(q.D0.LO) & bit) { /* If need to round up, */ + static dw10_t dwrnd; + LRHSET(dwrnd.HI, 0, 0); + LRHSET(dwrnd.LO, 0, bit); + q.D0 = x_dadd(q.D0, dwrnd); /* do it */ + DEBUGPRF(("ROUNDED UP! Bit %o\n", bit)); + if (i != (adffo(q.D0)-9)) { /* See if carried */ + DEBUGPRF(("CARRIED!\n")); + if (wskipl(q.D0.HI)) { /* Yes, into sign bit? */ + DEBUGPRF(("CARRIED INTO SIGN!\n")); + q.D0 = x_ashc(q.D0, -1); /* Yes, yecch! Special */ + op10m_signclr(q.D0.HI); /* handling to undo damage */ + --exp; + } else --i; /* Carried but compensation trivial */ + } + } + } + DEBUGPRF(("%sNORMALIZATION: %d\n", ((i != -8) ? "AB" : ""), i)); + + q.D0 = x_ashc(q.D0, i); /* Shift the fraction into place */ + exp -= i + 8; /* Adjust exponent for shift */ + + /* Fraction all done, just add exponent back now */ + LHSET(q.D0.HI, (LHGET(q.D0.HI) | ((h10_t)(exp & 0377) << 9))); +#if IFFLAGS + if (exp < 0) OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_FXU); + else if (exp > 0377) OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV); +#endif + + if (sign) + op10m_dmovn(q.D0); + return q.D0; +} + +static int qdivstep(qw10_t *aq, + register dw10_t d, + register int nmagbits) +{ + register qw10_t qw; + dw10_t quot; + register int qbit; + + /* Apply initial test - high order double must be less than abs val of + ** divisor, else overflow & no divide. + */ + qw = *aq; /* Get quadword */ + qw.D0 = x_dsub(qw.D0, d); + qbit = (wskipl(qw.D0.HI) ? 0 : 1); /* Get first quotient bit */ + if (qbit) + return 0; /* Overflow, no divide */ + + DEBUGPRF(("qdivstep: %lo,%lo,%lo,%lo|%lo,%lo,%lo,%lo %lo,%lo,%lo,%lo nmags=%d, qbit=%o\n", + DBGV_Q(qw), DBGV_D(d), nmagbits, qbit)); + + + /* First quotient bit (future sign bit) is always 0. + ** Because quotient is always positive, its left-shift in the loop + ** is simple enough to do inline. + */ + op10m_setz(quot.HI); /* Clear current & future signs */ + op10m_setz(quot.LO); /* Clear low word in case small N */ + while (--nmagbits >= 0) { /* Loop N times for N magnitude bits */ + qw = x_qash1(qw); /* Shift new bit into dividend */ + qw.D0 = (qbit ? x_dsub(qw.D0, d) : x_dadd(qw.D0, d)); + qbit = (wskipl(qw.D0.HI) ? 0 : 1); /* Get new quotient bit */ + + /* Shift quotient left by 1 */ + LSHIFTM(quot.HI, 1); /* Shift both words left 1 */ + LSHIFTM(quot.LO, 1); + if (wskipl(quot.LO)) /* If low sign now set, */ + op10m_iori(quot.HI, 1); /* that becomes low bit of high wd */ + + op10m_iori(quot.LO, qbit); /* Add qbit to right end of quotient */ + + DEBUGPRF(("qdivstep %d: D: %lo,%lo,%lo,%lo|%lo,%lo,%lo,%lo Q: %lo,%lo,%lo,%lo \n", + nmagbits, DBGV_Q(qw), DBGV_D(quot))); + } + op10m_signclr(quot.LO); /* Ensure low sign clear, compensate for + ** fact weren't doing true ASH in loop. + */ + + qw.D1 = qw.D0; /* Return quotient & remainder in right places */ + qw.D0 = quot; + *aq = qw; + return 1; +} + +#if OP10_GFMT + +/* Floating-point G-format Double-precision arithmetic. More moby hair! +*/ + +static dw10_t x_gfad(dw10_t, dw10_t, int); /* Aux for GFAD/GFSB */ +static dw10_t gfnorm(int, dw10_t, int); +static int aqfffrac(dw10_t, dw10_t); + + +dw10_t op10gfad(dw10_t a, dw10_t b) +{ + return x_gfad(a, b, 0); +} + +dw10_t op10gfsb(dw10_t a, dw10_t b) +{ + op10m_dmovn(b); /* This appears to work better?!?! */ + return x_gfad(a, b, 0); +} + +/* Common GFAD/GFSB routine. + + You'd expect G format to behave more or less the same as D +format. Well, welcome to reality. The G format implementation is +different in subtle ways which mainly reveal themselves in the handling +of unnormalized operands. This may be because it was a late addition +to the KL and must depend completely on ucode software rather than any +inherent hardware features (such as those that might exist for handling +F or D formats). + + In particular, it appears that if the exponent difference +between the operands is larger than 72, the KL simply normalizes the +largest operand and returns that, completely ignoring the other value. + + Also, seems to have same problem as DFAD/DFSB with regard to hanging +on to the lower bits for full normalization and rounding. + From empirical testing it appears that 2 additional bits are +needed from a "4th word", as for DFAD (36*3 + 3 real bits, or +24+35+35+2 = 96 total magnitude bits). Simplest to just use a full +extra 35-bit word; HOWEVER, its peculiar behavior in certain cases +suggests that lower bits beyond those 2 are not used in normalization, +yet are still used in rounding -- that is, if any "lost" bits are set, the +higher bits are immediately rounded prior to the normalization shift. +That's not quite right, but they definitely have SOME effect. + + Face it, the only way to emulate this miserable lossage +accurately would be to understand and do exactly what the KL ucode +does. But is that really a good idea? I mean, it just affects the low +bit, usually with unnormalized operands, and it would be much simpler +to just use better algorithms - which would return maximally accurate +results even with unnormalized operands, instead of spazzing in +mysterious ways. + +*/ + +static dw10_t x_gfad(register dw10_t a, register dw10_t b, int negb) +{ + register int i, expa, expb; + register uint18 rbit; /* May be 1<<17 */ + register dw10_t rd; + + GFSETUP(expa, a.HI); /* Set up exp & fract for A */ + GFSETUP(expb, b.HI); /* Set up exp & fract for B */ + + if (negb) /* If actually subtracting B, */ + op10m_dmovn(b); /* negate fraction now */ + + /* Now see which exponent is smaller; get larger in A, smaller in B. + ** This needs to be done even if one of the args is completely zero, so + ** that the other is set up in A for the normalization code. + */ + if ((i = expa - expb) < 0) { + dw10_t tmp; /* Swap args */ + int etmp; + etmp = expb, tmp = b; + expb = expa, b = a; + expa = etmp, a = tmp; + i = -i; + } + + /* Special KL G format crock. */ + if (i > 72) { + DEBUGPRF(("GFAD i>72 NORM: i=%d (%o-%o) A: %lo,%lo,%lo,%lo\n", + i, expa, expb, DBGV_D(a))); + return gfnorm(expa, a, 1); + } + + /* Now right-shift B by the number of bits in i, then add to A. */ + rd = x_ashc(b, 70-i); /* Get bottom 2 words of a 4-word ASHC */ + b = x_ashc(b, -i); /* Shift B for adding */ + DEBUGPRF(("GFAD A: (%o) %lo,%lo,%lo,%lo B: (%o) %lo,%lo,%lo,%lo i=%d\n", + expa, DBGV_D(a), expb, DBGV_D(b), i)); + + a = x_dadd(a, b); /* Add into A */ + + DEBUGPRF((" preN: (%o) %lo,%lo,%lo,%lo R: %lo,%lo,%lo,%lo\n", + expa, DBGV_D(a), DBGV_D(rd))); + + /* A now has sum, with the low-order 3rd word in RD's high word. + ** The sign of A is correct; that of RD is irrelevant. + ** Check high bits to determine normalization step, by looking + ** at the bits of the exponent field PLUS the high fraction bit. + */ + rbit = 0; + switch (LHGET(a.HI) >> (H10BITS-(GFEBITS+1))) { + case 000004>>2: /* Normalized positive fraction */ + DEBUGPRF(("WINNING POSITIVE\n")); + rbit = (LHGET(rd.HI) & (HSIGN>>1)); + break; + + case 000010>>2: /* Overflow of pos fraction */ + case 000014>>2: + DEBUGPRF(("OVERFLOW POSITIVE\n")); + rbit = RHGET(a.LO) & 01; + a = x_ashc(a, -1); /* Normalize by shifting right */ + ++expa; /* And adding one to exponent */ + break; + + case 077760>>2: /* Overflow of neg fraction, maybe zero mag */ + case 077764>>2: /* Overflow of neg fraction, nonzero mag */ + DEBUGPRF(("OVERFLOW NEGATIVE\n")); + rbit = RHGET(a.LO) & 01; + a = x_ashc(a, -1); /* First normalize into 077770 case, */ + rd = x_ashc(rd, -1); /* Shift RD as well, */ + if (rbit) /* Copy bit lost from A */ + LHSET(rd.HI, (LHGET(rd.HI) | (HSIGN>>1))); + else + LHSET(rd.HI, (LHGET(rd.HI) & ~(HSIGN>>1))); + ++expa; /* then fall thru below to check! */ + + case 077770>>2: /* Normalized negative fraction (probably) */ + rbit = LHGET(rd.HI) & (HSIGN>>1); + DEBUGPRF(("GFAD:NEG: (%o) %lo,%lo,%lo,%lo %lo,%lo,%lo,%lo r=%lo\n", + expa, DBGV_D(a), DBGV_D(rd), (long)rbit )); + + if (rbit && !(LHGET(rd.HI)&(HMASK>>2)) && !RHGET(rd.HI) + && !wmagskipn(rd.LO)) { + rbit = 0; /* No round if no other bits */ + DEBUGPRF(("NEG - RND DROPPED - ")); + } + if (op10m_tlnn(a.HI,GFFMASK) || RHGET(a.HI) + || rbit || wmagskipn(a.LO)) { + DEBUGPRF(("WINNING NEGATIVE\n")); + break; + } + DEBUGPRF(("OVERFLOW NEG (ZERO MAG)\n")); + rbit = 0; + LHSET(a.HI, 0777740); /* Pretend right-shifted 1 bit */ + ++expa; + break; + + /* Anything else is an unnormalized result and must be handled + ** using fully general case. + */ + case 0: + if (!wskipn(a.HI) && !wmagskipn(a.LO) && !wmagskipn(rd.HI)) { + DEBUGPRF(("GFAD: S: ZERO RESULT!\n")); + return dw10zero; + } + default: + /* Must mess with RD, sigh. Ensure it has correct sign bit, + ** then find the first fraction bit in the 3 (now 4) word sum. + */ + if (wskipl(a.HI)) { + op10m_signset(rd.HI); + if ((i = op10ffo(op10setcm(a.HI))) >= 36) { + if ((i = op10ffo(op10setcm(a.LO))) >= 36) { + /* op10m_signset(rd.HI); */ + if ((i = op10ffo(op10setcm(rd.HI))) >= 36) { + op10m_signset(rd.LO); + i = op10ffo(op10setcm(rd.LO)) + 70 + 35; + } else i += 70; + } else i += 35; + } + } else { + op10m_signclr(rd.HI); /* Clear sign bit */ + if ((i = op10ffo(a.HI)) >= 36) { + if ((i = op10ffo(a.LO)) >= 36) { + /* op10m_signclr(rd.HI); */ + if ((i = op10ffo(rd.HI)) >= 36) { + op10m_signclr(rd.LO); + i = op10ffo(rd.LO) + 70 + 35; + } else i += 70; + } else i += 35; + } + } + + /* i now has position of first fraction bit; move it into place. */ + i -= GFEBITS; /* Find actual shift to perform */ + DEBUGPRF(("RENORMALIZATION: %d ", i)); + + /* G format hack, check for shift of 72. This catches + ** the case of a negative number with all ones, as well as + ** limiting the positive all-zero case more severely than the + ** "case 0:" test above. + */ + if (i > 72) { + DEBUGPRF(("- i>72, RET 0\n")); + return dw10zero; + } + +#if 1 /* Test to see if just 2 extra bits are sufficient */ + + if (i > 35 && (LHGET(rd.LO) & 0300000)) { + a = x_ashc(a, 2); + op10m_tro(a.LO, (LHGET(rd.HI) >> (18-3)) & 03); + rd = x_ashc(rd, 2); + i -= 2; + expa -= 2; + +#if 1 + /* This code implements "immediate rounding" such that if + ** normalization would use any bits from rd.LO (specifically + ** the low 33 bits), they are zapped (as above) but + ** the high bit is used for an immediate roundup. + ** + ** Try using this only for negative numbers (gah) + */ + if (wskipl(a.HI) && (i > 35) && (LHGET(rd.LO)&(H10SIGN>>1))) { + DEBUGPRF((" - IMM ROUNDUP")); + op10m_inc(rd.HI); /* Round up */ + if (!wmagskipn(rd.HI)) /* Propagate */ + a = op10dfinc(a); + /* Ugh, must recompute i in case roundup changed it */ + i = aqfffrac(a, rd) - GFEBITS; + } + op10m_setz(rd.LO); +#endif + + } +#endif +#if 0 /* Test to see if extra 35 bits are sufficient. + ** Answer: Yes, but extra precision prevents emulating KL lossage. + */ + if (i >= 35) { + a = x_ashc(a, 35); + a.LO = rd.HI; + rd.HI = rd.LO; + i -= 35; + expa -= 35; + } +#endif + + b.LO = rd.HI; /* Set up for old code */ + b.HI = a.LO; /* Duplicate middle word for easy shift */ + if (i < 35) { /* Right shift or small left shift */ + a = x_ashc(a, i); + b = x_ashc(b, i); + if (i > 0) a.LO = b.HI; + } else if (i < 70) { /* One-word or larger shift */ + a = x_ashc(b, i-35); + op10m_setz(b.LO); + } else { /* Two-word or larger shift */ + a.HI = x_ash(b.LO, i-70); + op10m_setz(a.LO); + op10m_setz(b.LO); + } + + DEBUGPRF((" PostN S: %lo,%lo,%lo,%lo R: %lo,%lo", + DBGV_D(a), DBGV_W(b.LO))); + + /* Now find roundup bit, and do fixups for negative results */ + rbit = LHGET(b.LO) & (HSIGN>>1); /* Find roundup bit */ + if (wskipl(a.HI)) { /* Special checks for neg */ + if (!(LHGET(b.LO)&(HMASK>>2)) && !RHGET(b.LO)) { + rbit = 0; /* No round if no other bits */ + DEBUGPRF((" - NEG RND DROPPED")); + } + if (LHGET(a.HI) == 0777700 && !RHGET(a.HI) && !wmagskipn(a.LO) + && !rbit) { + LHSET(a.HI, 0777740); /* Effect an ASHC -1 */ + ++expa; /* Compensate for right-shft */ + DEBUGPRF((" - NEG ZERO-MAG FIXUP")); + } + } + DEBUGPRF(("\n")); + expa -= i; /* Adjust exponent */ + break; + } + + /* At this point, number in A is normalized, and rbit is set if a + ** roundup increment must be done (which in rare cases can trigger + ** a single-shift renormalization). + */ + if (rbit) { + DEBUGPRF(("ROUNDUP DONE")); + a = op10dfinc(a); /* Add then check for overflow */ + if (wskipl(a.HI)) { /* Negatives are messy */ + if (op10m_tlnn(a.HI,GFFHIBIT)) { /* If first fract bit is "wrong" */ + if (op10m_tlnn(a.HI, GFFMAGS) /* See if really OK */ + || RHGET(a.HI) || wmagskipn(a.LO)) { + --expa, a = x_ashc(a, 1); /* Nope, shift up */ + DEBUGPRF((" - NEG RENORMALIZED!")); + } + } + } else if (!op10m_tlnn(a.HI, GFFHIBIT)) { + ++expa, LHSET(a.HI, GFFHIBIT); /* Positive overflow, fix up */ + DEBUGPRF((" - POS RENORMALIZED!")); + } + DEBUGPRF(("\n")); + } + + DEBUGPRF(("GFAD: S: (%o) %lo,%lo,%lo,%lo\n", expa, DBGV_D(a))); + + /* Ta da! Put exponent back in (it may have underflowed or overflowed + ** but nothing we can do about it). + */ + if (wskipl(a.HI)) /* If sign set, put ones-complement of exp */ + LHSET(a.HI, (LHGET(a.HI) ^ ((h10_t)(expa & GFEMAGS) << GFFBITS))); + else LHSET(a.HI, (LHGET(a.HI) | ((h10_t)(expa & GFEMAGS) << GFFBITS))); +#if IFFLAGS + if (expa < 0) OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_FXU); + else if (expa > GFEMAGS) OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV); +#endif + op10m_signclr(a.LO); /* Double flt always zeros low sign */ + return a; +} + +#if 1 /* Only needed for special KL-lossage-emulation experiment */ + +static int aqfffrac(register dw10_t a, register dw10_t rd) +{ + register int i; + + if (wskipl(a.HI)) { + op10m_signset(rd.HI); + if ((i = op10ffo(op10setcm(a.HI))) >= 36) { + if ((i = op10ffo(op10setcm(a.LO))) >= 36) { + /* op10m_signset(rd.HI); */ + if ((i = op10ffo(op10setcm(rd.HI))) >= 36) { + op10m_signset(rd.LO); + i = op10ffo(op10setcm(rd.LO)) + 70 + 35; + } else i += 70; + } else i += 35; + } + } else { + op10m_signclr(rd.HI); /* Clear sign bit */ + if ((i = op10ffo(a.HI)) >= 36) { + if ((i = op10ffo(a.LO)) >= 36) { + /* op10m_signclr(rd.HI); */ + if ((i = op10ffo(rd.HI)) >= 36) { + op10m_signclr(rd.LO); + i = op10ffo(rd.LO) + 70 + 35; + } else i += 70; + } else i += 35; + } + } + return i; +} +#endif /* special hack */ + + +/* GFMP on the real KL has some problems with failure to set the floating + underflow flag in some situations. + For example, this test: +VERIFY ERROR: "gfmp" + Input: gfmp 40,0,0,2 777740,0,0,0 => 600037,777777,377777,777776 [440000] + E Ver: gfmp 40,0,0,2 777740,0,0,0 => 600037,777777,377777,777776 [440100] + + The emulation result (floating underflow) is correct. +*/ + +dw10_t op10gfmp(register dw10_t a, register dw10_t b) +{ + register qw10_t q; + register int exp, expb; + register int sign; + int signb; + + /* Make operands positive and remember sign of result */ + GF_DPOSSETUP(sign, exp, a); + GF_DPOSSETUP(signb, expb, b); + + DEBUGPRF(("GFMP: A: (%o) %lo,%lo,%lo,%lo B: (%o) %lo,%lo,%lo,%lo\n", + exp, DBGV_D(a), expb, DBGV_D(b))); + + sign ^= signb; /* Set 0 if both were negative */ + exp += expb - GFEEXCESS; /* Add exponents to effect multiply */ + + /* Now have two 59-bit magnitude positive numbers. Multiply them + ** to get 118 bits of product in 4 words. + */ + q = x_dmul(a, b); /* Multiply the numbers, get quad result */ + + DEBUGPRF(("GFMP Q: %lo,%lo,%lo,%lo|%lo,%lo,%lo,%lo exp=%o (%d.)\n", + DBGV_Q(q), exp, exp)); + + /* Since there are 118 bits of product with 35 magnitude bits in each + ** word, the high word should contain 118-105 = 13 high mag bits. + ** Thus the top 36-13=23 bits of product should be empty, and if + ** everything was normalized, the next bit (B23, start of fraction) + ** should be 1 and we just shift everything left by 23-12=11 bits to get + ** things into place. + ** If not, must normalize by looking for first bit of fraction, + ** which is a painfully slow process. + */ + if (!op10m_trnn(q.D0.HI, 1<<(H10BITS-(1+23-H10BITS)))) { /* Test B23 */ + /* No high fraction bits set... see if result was 0 */ + if (!RHGET(q.D0.HI) && !wskipn(q.D0.LO) && !dskipn(q.D1)) + return q.D0; /* Zero product, return 0.0 */ + do { + q = x_qash1(q); /* ASH quad up one bit */ + --exp; /* Adjust exponent accordingly */ + } while (!op10m_trnn(q.D0.HI, 1<<(H10BITS-(1+23-H10BITS)))); + /* Until get high fract bit */ + } + + /* Fraction in expected place, ASH 23-12=11 bits left to align for exp */ + q.D0 = x_ashc(q.D0, 11); /* Do ASHC bringing in 11 zeros */ + op10m_iori(q.D0.LO, /* Add 11 bits from 3rd wd */ + (LHGET(q.D1.HI) >> ((HBITS-1)-11))); + + /* Now do rounding. See if bit lower than LSB is set (this is the next + ** bit after the ones added from 3rd word). + ** If so, add one to LSB. This will overflow only if the fraction was + ** all ones, resulting in a roundup to all zeros except the low exponent + ** bit. If so, re-normalize by simply resetting the LH to right value + ** and adjusting the exponent. + */ + if (LHGET(q.D1.HI) & (1 << ((HBITS-1)-(11+1))) ) { + q.D0 = op10dfinc(q.D0); + if (op10m_tlnn(q.D0.HI, GFELHF)) { /* If carried out of fract */ + LHSET(q.D0.HI, GFFHIBIT); /* reset to proper value */ + ++exp; /* and adjust exponent */ + } + } + + /* Fraction all done, just add exponent back now */ + LHSET(q.D0.HI, (LHGET(q.D0.HI) | ((h10_t)(exp & GFEMAGS) << GFFBITS))); +#if IFFLAGS + if (exp < 0) OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_FXU); + else if (exp > GFEMAGS) OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV); +#endif + + if (sign) + op10m_dmovn(q.D0); + return q.D0; +} + +/* GFDV - note no-divide case simply returns 1st arg instead of +** failing explicitly. +*/ + +dw10_t op10gfdv(register dw10_t a, register dw10_t b) +{ + qw10_t q; + register int exp; + register int expb, sign, signb; + dw10_t origa; + + /* Make operands positive and remember sign of result */ + /* The odd code here is similar to that for DFDV for the same reasons. + */ + GF_DPOSSETUP(signb, expb, b); /* Set up 2nd arg normally */ + if (!wskipn(b.HI) && !wmagskipn(b.LO)) { /* Check for zero divisor */ + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_DIV); + return a; /* Ugh, No Divide error. Return original op */ + } + + /* Now set up A. Note a different mechanism is used in order to emulate + ** the way that the KL apparently chops off all exponent bits when + ** extracting the fraction bits, thus turning an unnormalized negative + ** number of (e.g.) 777000,,0 into 0,,0 instead of 1000,,0. + **/ + origa = a; /* Save original value of A */ + if (wskipl(a.HI)) { + op10m_dmovn(a); + if (wskipl(a.HI)) { + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_DIV); + return origa; /* Ugh, No Divide error. Return original op */ + } + sign = !signb; + } else sign = signb; + exp = GFEGET(a.HI); /* Get sign and exponent */ + op10m_tlz(a.HI, GFELHF); /* Chop out exponent bits */ + if (!wskipn(a.HI) && !wmagskipn(a.LO)) + return dw10zero; /* No fraction bits, return 0 */ + + + DEBUGPRF(("GFDV: A: (%o) %lo,%lo,%lo,%lo B: (%o) %lo,%lo,%lo,%lo\n", + exp, DBGV_D(a), expb, DBGV_D(b))); + + exp += GFEEXCESS - expb; /* Do division on exponents */ + + /* For G-Format, 59-bit fracts */ + + /* Now have two 59-bit magnitude numbers; calculate A/B. + ** In order to ensure that the division can succeed, we pre-multiply the + ** divisor by 2. This is compensated for by adjusting the final exponent. + ** This can fail with No Divide if the operands aren't normalized. + ** + ** Note that the udcmpl test relies on both args being positive and the + ** low sign bit being clear. + */ + op10m_signclr(a.LO); /* Ensure valid for udcmpl test */ + op10m_signclr(b.LO); + if (!op10m_udcmpl(a, b)) { /* If a >= b */ + DEBUGPRF(("GFDV Divisor pre-multiply!\n")); + b = x_ashc(b, 1); /* Make divisor be bigger */ + ++exp; + if (!op10m_udcmpl(a, b)) { /* If a >= b */ + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_DIV); + return origa; /* Ugh, No Divide error. Return original op */ + } + } + q.D0 = a; + q.D1 = dw10zero; /* Set up dividend */ + + if (!qdivstep(&q, b, 59+1)) { + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_DIV); + return a; + } + + /* Because the fractions are aligned, the result should be aligned as + ** well. However, because of the extra divide step needed to compute + ** the rounding bit, the result is left-shifted 1 bit greater than + ** it should be. Adjust for this by decrementing the exponent. + */ + --exp; + + DEBUGPRF(("GFDV: Q: %lo,%lo,%lo,%lo R: %lo,%lo,%lo,%lo exp=%o (%d.)\n", + DBGV_Q(q), exp, exp)); + + if (!dskipn(q.D0)) /* Check for zero result */ + return q.D0; + + /* To compensate for possible unnormalized operands and roundup carry, + ** we do a completely general normalization and round. Note we've already + ** tested for 0 so bits definitely exist. + ** If an exponent under/overflow happens, GFNORM will catch it. + */ + if (sign) { + a = gfnorm(exp, q.D0, 1); /* Normalize and round */ + op10m_dmovn(a); + return a; + } + return gfnorm(exp, q.D0, 1); /* Normalize and round */ +} +#endif /* OP10_GFMT */ + +/* Floating-point Double-precision Conversions */ + +#if OP10_GFMT + +/* GFNORM - Returns normalized G-format floating point, given +** double-word integer fraction and exponent to use if fraction were +** already in proper position (first significant bit at B12) +** Sets flags if exponent overflows. +*/ +static dw10_t gfnorm(register int exp, + register dw10_t d, /* Contains signed integer fraction */ + int rnd) /* TRUE to round result */ +{ + register int i, rbit; /* rbit only needs 12 bits, so "int" safe */ + register int sign; + + /* This algorithm interprets the fraction as a signed double integer + ** and converts it immediately to positive form, if negative. + ** This is much easier than trying to do the right thing for negatives + ** all through the code. See the old code for sfnorm() to see what I mean. + */ + + /* Check for negative fraction and convert to use + ** positive value instead. Note that the maximum negative number + ** cannot be made positive, but this is compensated for by + ** adjusting its exponent and fraction. + ** sfnorm() gets away with doing nothing because it can use a logical + ** shift. This won't work here because ASHC looks at the sign bit, + ** hence the trickery here to ensure the sign is always 0. + */ + if (sign = wskipl(d.HI)) { + op10m_dmovn(d); /* Negate the fraction. */ + if (wskipl(d.HI)) { /* If still negative, */ + ++exp; /* adjust exponent and value */ + LHSET(d.HI, H10SIGN>>1); /* to get positive # */ + } + } + i = adffo(d); /* Find first 1-bit in double, skip low sign */ + + DEBUGPRF(("GFNORM: %lo,%lo,%lo,%lo i=%d, exp=%o\n", + DBGV_D(d), i, exp)); + if (i >= 71) + return dw10zero; /* No sig bits, force result to zero */ + + /* i now has position of first fraction bit; move it into place. + */ + i -= GFEBITS; /* Make relative to proper position, B12 */ + exp -= i; /* Adjust exponent */ + if (rnd && i < 0) /* If rounding OK and losing bits */ + rbit = RHGET(d.LO) /* Find value of last bit that will be lost */ + & (1 << ((-i)-1)); + else rbit = 0; + + if (i) + d = x_ashc(d, i); /* Now do double arithmetic shift! */ + + DEBUGPRF(("GNORMED: %lo,%lo,%lo,%lo exp=%d, rbit=%o\n", + DBGV_D(d), exp, rbit)); + + /* At this point, number in w is normalized, and rbit is set if a + ** roundup increment must be done. In the rare case that this + ** increment overflows, its value will be 100,,0 and will trigger + ** a single-shift renormalization. + */ + if (rbit) { + d = op10dfinc(d); /* Add then check for overflow */ + /* (above call sets low sign, but it gets flushed later) */ + + if (LHGET(d.HI) > GFFMASK) { + ++exp; /* Positive overflow, fix up */ + LHSET(d.HI, GFFHIBIT); + DEBUGPRF(("ROUNDUP - RENORMED!\n")); + } else + DEBUGPRF(("ROUNDUP\n")); + } + + /* Fraction is normalized with exponent bits clear, can just OR + ** exponent back in, and negate if needed to finish off result. + */ + op10m_tlo(d.HI, ((h10_t)(exp & GFEMAGS) << GFFBITS)); + if (sign) + op10m_dmovn(d); /* This forces low sign 0 as well */ + else + op10m_signclr(d.LO); /* Unsure, so force low sign 0 */ + + /* Ta da! Check before returning for overflow or underflow. + ** Note that the negation, if one, can never cause either since a + ** positive number can always be negated. + */ + +#if IFFLAGS + if (exp < 0) OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_FXU); + else if (exp > GFEMAGS) OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV); +#endif + + return d; +} + + +/* GFSC - G Format floating scale. +** Note peculiarity wherein GFSC tests to see if the first word is zero. +** It should actually test the MAGNITUDE of the FRACTION, meaning +** that if the number is negative, the test is made for zero bits +** rather than one bits. +** However, the real machine appears to always check for zero bits +** in the ENTIRE word, including the sign bit. So a negative number +** or a non-zero exponent never triggers this case, regardless of +** whether its high word fraction is all ones or all zeroes. Sigh. +** +** GFSC on a KL screws up on the flags when given an E of 400000 applied to +** a fairly low exponent. It sets Overflow and Floating Overflow, +** but not Floating Underflow! +** This code correctly sets Floating Underflow. +** (Oddly enough, FSC also messes this case up, but in a different way!) +** +** A real machine also screws up peculiarly in another way: +** VERIFY ERROR: "gfsc" +** Input: gfsc 777777,777777,777777,777777 100 => 777200,0,20,0 [0] +** E Ver: gfsc 777777,777777,777777,777777 100 => 777140,0,0,0 [0] +** Note the bogus "20" in the low LH! +** Admittedly the argument is unnormalized, but this is still weird. +*/ +dw10_t +op10gfsc(register dw10_t d, + register h10_t h) +{ + register int exp; + +#if 1 /* See note above about this stupid test. */ + if (!wskipn(d.HI)) + return dw10zero; /* High word, return all zeros */ +#endif + + /* Canonicalize exponent and fraction */ + GFSETUP(exp, d.HI); /* Set up exp and fraction */ + + /* Do special check of 1st wd fractional part. + ** Note that at this point the exponent has been replaced by copies of + ** the sign bit, so the test is simplified. + */ +#if 0 + if (wskipl(d.HI)) { + register dw10_t nd; + nd = d; + op10m_dmovn(nd); /* Do a double negate, then test */ + if (!wskipn(nd.HI)) + return dw10zero; + } else + if (!wskipn(d.HI)) + return dw10zero; /* High fraction zero, return all zeros */ +#endif + + /* Scale up exponent. Note immediate arg is taken as 11 bits, not + ** 8 bits as for all other similar ops! (fsc, lsh, etc) + */ + exp += (((h)&HSIGN) ? ((int)(h)|~03777) : ((int)(h)&03777)); + + return gfnorm(exp, d, 0); /* Put G-float together, don't round */ +} + +/* GSNGL - G Format to Single-precision Format, with round. +** +** Note: According to PRM, AC is unchanged if the G exponent is out of +** range to begin with. However, this appears not to be always true, per +** following counter-example: +** VERIFY ERROR: "gsngl" +** Input: gsngl 157373,175573,76600,173751 => 373731,755732 [440100] +** E Ver: gsngl 157373,175573,76600,173751 => 157373,175573 [440100] +** +** Note result was clobbered. Doesn't even have the excuse of +** unnormalized arg for this one! +*/ +/* G -> S, with round, TRUE if succeeded */ +int +op10xgsngl(register dw10_t *ad) +{ + register int exp; + register dw10_t d; + + d = *ad; /* Fetch the double */ + + /* Canonicalize exponent and fraction */ + GFSETUP(exp, d.HI); /* Set up exp and fraction */ + + /* Special check for zero, needed to avoid setting flags */ + if (!exp && !wskipn(d.HI)) { + ad->w[0] = w10zero; + return 1; + } + + /* Convert to new exponent range */ + exp = (exp - GFEEXCESS) + SFEEXCESS; + + /* Now see if OK for single-prec */ + DEBUGPRF(("GSNGL: new exp=%o (%d.)\n", exp, exp)); + if (exp < 0) { + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV+PCF_FXU); + return 0; + } else if (exp > SFEMAGS) { + OP10_PCFSET(PCF_ARO+PCF_TR1+PCF_FOV); + return 0; + } + + /* Now what? PRM seems to imply no normalization is done, just + ** rounding. However, it seems best to carry out a fully general + ** normalize and round. + ** Need to find out what real machine does. + */ + + /* The following code uses cleverness to take advantage of + ** existing single-prec renormalization & round. + ** First we shift the fraction up by 10 bits, not 3, so as to take + ** maximum advantage of any rounding from the low bits. This is + ** particularly necessary for negative numbers, otherwise it's possible + ** for sfnorm's neg->pos algorithm to get a spurious roundup for an + ** all-ones fraction. + ** This is then fed to sfnorm(), with the new exponent adjusted down + ** by 10-3 to compensate for the extra bit shifts; sfnorm() knows how to + ** do a right-shift to put things back in place. + ** sfnorm() will set the flags if rounding overflows, just as PRM says. + */ + d = x_ashc(d, 3+7); /* Shift double fraction 10 places left */ + + ad->w[0] = sfnorm(exp-7, d.HI, 1); /* Return result in 1st wd of dbl */ + return 1; +} + +/* S -> G */ + +dw10_t +op10gdble(register w10_t w) +{ + register dw10_t d; + register int exp; + register int sign; + + SF_POSSETUP(sign, exp, w); /* Set up sign, exponent and fraction */ + d.HI = w; /* Set high word */ + op10m_setz(d.LO); /* Clear low */ + + /* See if fraction is already normalized, to save the time of + ** a full renormalization call. + */ + if (op10m_tlnn(d.HI, SFFHIBIT)) { /* Normalized? Most common case */ + d = x_ashc(d, -3); /* Right-shift 3 bits */ + exp = (exp - SFEEXCESS) + GFEEXCESS; /* Convert up to G exponent */ + LHSET(d.HI, (LHGET(d.HI) | ((h10_t)(exp) << (H10BITS-GFEBITS)))); + } else if (!wskipn(d.HI)) { /* Zero? Next most common */ + return d; + } else { + /* Either unnormalized operand, or fraction is negative 400,,0. + ** Invoke slow but fully general normalization. + */ + d = gfnorm(((exp - SFEEXCESS) + GFEEXCESS - 3), d, 0); + } + if (sign) /* If necessary, convert back */ + op10m_dmovn(d); /* Note low sign always left 0 */ + return d; +} + +/* Dfix -> G, with round */ + +dw10_t +op10dgfltr(register dw10_t d) +{ + /* Double already in arg, just need to provide normalizer with + ** right exponent. Binary point is GFFBITS+H10BITS+35 away from + ** normal position. + ** Note also that gfnorm() correctly handles max-neg-# case! + */ + return gfnorm(GFEEXCESS+GFFBITS+H10BITS+35, d, 1); +} + +/* fix -> G, with round (not really) */ + +dw10_t +op10gfltr(register w10_t w) +{ + register dw10_t d; + + d.HI = w; /* Set up double-word */ + op10m_setz(d.LO); + + /* Provide exponent & normalize it. + ** Binary point of the single-prec fix is to right of 1st word. + ** This is GFFBITS+H10BITS (ie all fraction bits in 1st word) + ** away from desired position, so add those to exponent. + */ + return gfnorm(GFEEXCESS+GFFBITS+H10BITS, d, 0); +} + + +/* The following 2 functions are placeholders for GFIX{R} and GDFIX{R}, +** which on a real KL are actually simulated in the monitor. +** These functions HAVE NOT BEEN VERIFIED yet! +*/ + +/* G -> Dfix, TRUE if succeeded */ + +int +op10xgdfix(register dw10_t *ad, int rnd) +{ + register dw10_t d = *ad; + register int i, rbit, sign; + + GF_DPOSSETUP(sign, i, d); /* Set up exponent, get positive fract */ + + /* Apply exponent size test now */ + if (i > (GFEEXCESS+70)) { /* If integer wd be too big, */ + OP10_PCFSET(PCF_ARO+PCF_TR1); /* set flags */ + return 0; /* and return arg unchanged */ + } + + /* OK to proceed! + ** Fraction is normally a 24+35= 59-bit positive integer. It will be a + ** 60-bit value of 100,,0 ? 0 only if the float was negative with no + ** one-bits in the fraction part (which is an unnormalized value). + */ + i -= (GFEEXCESS+59); /* Get # bits to shift fraction */ + if (i <= -60) { /* If shifting it to oblivion */ + *ad = dw10zero; /* just return zero, skip rigmarole */ + return 1; + } + + if (rnd && i < 0) { /* If rounding OK and losing bits */ + /* Find value of last bit that will be lost */ + if (i <= -35) { /* Bit in high word? */ + if (i <= -(35+H10BITS)) /* Bit comes from High LH? */ + rbit = LHGET(d.HI) + & (1 << ((-i)-(35+H10BITS+1))); + else /* Bit comes from High RH */ + rbit = RHGET(d.HI) + & (1 << ((-i)-(35+1))); + } else { /* Bit in low word (shift -35 < i < 0) */ + if (i <= -H10BITS) /* Bit comes from Low LH? */ + rbit = LHGET(d.LO) + & (1 << ((-i)-(H10BITS+1))); + else /* Bit comes from Low RH */ + rbit = RHGET(d.LO) + & (1 << ((-i)-1)); + } + } else rbit = 0; + + if (i) + d = x_ashc(d, i); /* Do the shift */ + + if (rbit) { /* Need any rounding? */ + op10m_inc(d.LO); /* Yep, trivial */ + if (!wmagskipn(d.LO)) /* Check for carry to high */ + op10m_inc(d.HI); /* Will never overflow this one */ + } + + /* Since returning a double fix, make sure result conforms to fixed-point + ** sign convention (low sign is copy of high's) + */ + if (sign) + op10m_dneg(d); /* Negate (sets low sign) */ + else + op10m_signclr(d.LO); /* Else clear low sign */ + + *ad = d; /* Return result */ + return 1; /* Say succeeded */ +} + + +/* G -> fix, TRUE if succeeded */ + +int +op10xgfix(register dw10_t *ad, int rnd) +{ + /* See code for op10xgdfix() above (and op10xfix()) for comments. + ** This is a concise version. + */ + register dw10_t d = *ad; + register int i, rbit, sign; + + GF_DPOSSETUP(sign, i, d); /* Set up exponent, get positive fract */ + if (i > (GFEEXCESS+35)) { /* If integer wd be too big, */ + OP10_PCFSET(PCF_ARO+PCF_TR1); /* set flags */ + return 0; /* and return arg unchanged */ + } + i -= (GFEEXCESS+35); /* Get # bits to shift fraction */ + if (i <= -36) { /* If shifting it to oblivion */ + ad->HI = w10zero; /* just return zero, skip rigmarole */ + return 1; + } + + if (rnd && i < 0) { /* If rounding OK and losing bits */ + /* Find value of last bit that will be lost */ + if (i <= -35) { /* Bit in high word? */ + if (i <= -(35+H10BITS)) /* Bit comes from High LH? */ + rbit = LHGET(d.HI) + & (1 << ((-i)-(35+H10BITS+1))); + else /* Bit comes from High RH */ + rbit = RHGET(d.HI) + & (1 << ((-i)-(35+1))); + } else { /* Bit in low word (shift -35 < i < 0) */ + if (i <= -H10BITS) /* Bit comes from Low LH? */ + rbit = LHGET(d.LO) + & (1 << ((-i)-(H10BITS+1))); + else /* Bit comes from Low RH */ + rbit = RHGET(d.LO) + & (1 << ((-i)-1)); + } + } else rbit = 0; + + if (i) + d = x_ashc(d, i); /* Do the shift */ + + if (rbit) { /* Need any rounding? */ + op10m_inc(d.HI); /* Yep, trivial */ + if (!wmagskipn(d.HI)) { /* Check for carry out */ + if (!sign) { /* Ugh. OK if result will be negative, */ + OP10_PCFSET(PCF_ARO+PCF_TR1); /* else set flags */ + return 0; /* and return arg unchanged */ + } + } + } + + /* Since returning a double fix, make sure result conforms to fixed-point + ** sign convention (low sign is copy of high's) + */ + if (sign) + op10m_movn(d.HI); /* Negate (sets low sign) */ + + ad->HI = d.HI; /* Return result */ + return 1; /* Say succeeded */ +} + +#endif /* OP10_GFMT */ + +/* Old obsolete operations for PDP-6, KA-10, KI-10. +*/ + +/* DFN - Double Floating Negate +** No flags are set. "Negating a correctly formatted floating point +** number cannot cause overflow". +*/ +dw10_t op10dfn(register dw10_t d) +{ + register h10_t h; + + op10m_setcm(d.HI); /* Complement high order word */ + h = (LHGET(d.LO) ^ HMASK) & (HMASK>>9); /* Ditto low but clear sign&exp */ + RHSET(d.LO, (-RHGET(d.LO)) & HMASK); /* Negate low RH */ + if (!RHGET(d.LO)) + /* If RH clear, fix up LH, but avoid sign & exp bits!! */ + if (!(h = (h+1) & (HMASK>>9))) { /* If need more, */ + op10m_inc(d.HI); /* increment hi wd */ + } + /* Combine low-order LH back in, must leave high 9 bits alone. + ** The high 9 bits of H are already clear. + */ + LHSET(d.LO, ((LHGET(d.LO) & ~(HMASK>>9)) | h)); + return d; +} + +/* UFA - Unnormalized Floating Add +** Similar enough to FAD that can use the same code! ffadr() +** may normalize 1 bit if an overflow happens, which is the same +** thing that UFA does. +*/ +w10_t op10ufa(register w10_t a, register w10_t b) +{ + return ffadr(a, b, FADF_NONORM|FADF_NORND); /* No norm, no round */ +} + + +/* Long form single-precision (immediate mode of FAD, FSB, FMP, FDV) */ + +static dw10_t dtokad(register dw10_t d) /* (double) -> (KA double) */ +{ + register int exp; + + op10m_signclr(d.LO); /* Sign shd be 0, but make sure */ + RSHIFTM(d.LO, 8); /* Make room for exponent */ + if (wskipn(d.LO)) { + exp = SFEGET(d.HI); /* Get sign and exponent */ + if (exp & SFESIGN) { /* Negative? */ + exp = exp ^ SFEMASK; /* If so, get ones-complement */ + } + if (exp < 27) /* If exponent too small, */ + op10m_setz(d.LO); /* just clear 2nd word */ + else /* Ah, put 2nd exp into low wd */ + LHSET(d.LO, (LHGET(d.LO) | ((h10_t)(exp-27) << SFFBITS))); + } + return d; +} + +dw10_t op10fadl(register w10_t a, register w10_t b) +{ + return dtokad(dfad(op10fdble(a), op10fdble(b), 0)); +} + +dw10_t op10fsbl(register w10_t a, register w10_t b) +{ + return dtokad(dfad(op10fdble(a), op10fdble(b), 1)); +} + +/* FMPL - This implementation differs when the exponent overflows. +** Hardware clears the low word; this code still attempts to set +** the exponent. +*/ +dw10_t op10fmpl(register w10_t a, register w10_t b) +{ + return dtokad(op10dfmp(op10fdble(a), op10fdble(b))); +} + +/* FDVL - I'm not positive this emulates the hardware precisely for +** abnormal cases. In particular, no-divide probably fails. +*/ +dw10_t op10fdvl(register dw10_t d, + register w10_t w) +{ + /* Convert from KA software double to hardware double */ + LSHIFTM(d.LO, 8); + op10m_signclr(d.LO); /* Ensure sign flushed */ + return dtokad(op10dfdv(d, op10fdble(w))); +} + +/* OP10FDBLE - Converts Float to Double Float. +** Auxiliary for Long-form instructions above, also a KCC C simop +** in its own right. +*/ +/* (double)(float)w */ +dw10_t op10fdble(register w10_t w) +{ + dw10_t d; + d.HI = w; + op10m_setz(d.LO); + return d; +} + +/* Floating-point conversions to and from native platform format. +*/ +#if 0 /* Not needed currently - eventually for KCC only? */ + +#include /* For native assistance */ + +/* OP10_DPUTF - Native double float to single-word PDP-10 float +*/ +w10_t op10_dputf(dbl) +double dbl; +{ + register w10_t w; + register uint32 imant; + int exp, neg = 0; + + if (dbl <= 0) { /* Get magnitude for easier hacking */ + if (dbl == 0) + return w10zero; + neg++; + dbl = -dbl; + if (dbl < 0) + return w10maxneg; + } + /* Split value into normalized exp and fraction, then + ** floating-shift fraction so we have 27 binary digits when the double + ** is converted into an integer. + */ + imant = (int32)ldexp(frexp(dbl, &exp), 27); /* Get 27-bit integ fraction */ + + /* Form high half from exponent and high fraction. It is perfectly + ** possible for the exponent to overflow -- tough. + */ + LHSET(w, (((h10_t)((exp+0200)&0377)<<9) | ((imant>>18)&0777))); + RHSET(w, (imant & HMASK)); + if (neg) + op10m_movn(w); + return w; +} + +/* OP10_FGETD - Single-word PDP-10 float to native double float +*/ +double op10_fgetd(w) +register w10_t w; +{ + register uint32 imant; + register int exp; + register double dbl; + int neg = 0; + + if (wskipl(w)) { + neg++; + op10m_movn(w); + } + imant = op10wtos(w) & (((uint32)1<<27)-1); /* Get 27-bit fraction */ + exp = (LHGET(w) >> 9) & 0377; /* Get 8-bit exponent */ + if (!exp && !imant) return 0.0; + dbl = ldexp((double)imant, (exp-0200)-27); /* Put it together */ + return neg ? -dbl : dbl; +} + + +/* OP10_DPUTD - Native double float to double-word PDP-10 double +*/ +static dw10_t op10_dputd(dbl) +double dbl; +{ +#if 0 +#endif +} + +/* OP10_DGETD - Double-word PDP-10 doublefloat to native double float +*/ +static double op10_dgetd(d) +register dw10_t d; +{ +#if 0 + register uint32 imant; + register int exp; + register double dbl; + int neg = 0; + + if (wskipl(w)) { + neg++; + op10m_movn(w); + } + imant = op10wtos(w) & (((uint32)1<<27)-1); /* Get 27-bit fraction */ + exp = (LHGET(w) >> 9) & 0377; /* Get 8-bit exponent */ + if (!exp && !imant) return 0.0; + dbl = ldexp((double)imant, (exp-0200)-27); /* Put it together */ + return neg ? -dbl : dbl; +#endif +} +#endif /* 0 */ + +/* Simulated Ops for KCC C operations (not actual PDP-10 ops) */ + +#if OP10_KCCOPS + +/* Comparisons and testing */ + +/* C simop: Gives result < = > 0 for A-B */ + +int op10cmp(register w10_t a, register w10_t b) +{ + return op10m_caml(a,b) ? -1 : op10m_camn(a,b); +} + +/* C simop: Gives result < = > 0 for unsigned A-B */ + +int op10ucmp(register w10_t a, register w10_t b) +{ + return op10m_ucmpl(a,b) ? -1 : op10m_camn(a,b); +} + +/* C simop: Gives result < = > 0 for W */ + +int op10tst(register w10_t w) +{ + return op10m_skipl(w) ? -1 : op10m_skipn(w); +} + +/* C simop: Gives result < = > 0 for DW A-B */ +** Double compare is a bit weird since sign of low word must be ignored. +*/ +int op10dcmp(register dw10_t da, register dw10_t db) +{ + register int i; + if (i = op10cmp(da.HI, db.HI)) + return i; + op10m_signclr(da.LO); /* Clear sign bits of low words */ + op10m_signclr(db.LO); + return op10cmp(da.LO, db.LO); +} + +/* C simop: Gives result < = > 0 for doubleword */ + +int op10dtst(register dw10_t d) +{ + return op10m_signtst(d.HI) ? -1 : dskipn(d); +} + +/* C simop: Double fix negate */ + +dw10_t op10dneg(register dw10_t d) +{ + op10m_dneg(d); + return d; +} + + +/* C simop: Unsigned Multiply */ + +w10_t op10uimul(w10_t a, w10_t b) +{ + dw10_t d; + d = op10xmul(a, b); + if (RHGET(d.HI) & 01) /* If low bit of high wd is set, */ + op10m_signset(d.LO); /* set high bit of low wd */ + return d.LO; +} + +/* C simop: Unsigned Divide */ + +dw10_t op10uidiv(register w10_t a, register w10_t b) +{ + dw10_t db; + qw10_t q; + + if (!wskipl(a) && !wskipl(b)) + return op10idiv(a, b); + + /* Punt for now by doing double fix division */ + q.D0 = dw10zero; + q.D1.HI = wskipl(a) ? w10one : w10zero; + q.D1.LO = a; + db.HI = wskipl(b) ? w10one : w10zero; + db.LO = b; + q = op10ddiv(q, db); + db.HI = q.D0.LO; + db.LO = q.D1.LO; + return db; +} + +/* C simop: (float)(unsigned)w */ + +w10_t op10uflt(register w10_t w) +{ + if (wskipl(w)) + return sfnorm(0200+27+1, op10lsh(w, -1), 1); + return sfnorm(0200+27, w, 1); /* Normalize and round */ +} + +/* C simop: (int)(double)d */ + +w10_t op10dfix(register dw10_t d) +{ + register int exp, sign; + + SF_DPOSSETUP(sign, exp, d); /* Extract parts and set up positive fract */ + d = x_ashc(d, exp - 0233); + if (sign) + op10m_dmovn(d); + return d.HI; +} + +/* C simop: (double)(int)w */ + +dw10_t op10dflt(register w10_t w) +{ + register dw10_t d; + d.HI = w; + op10m_setz(d.LO); + d = x_ashc(d, -8); /* Make room for exponent */ + LHSET(d.HI, (LHGET(d.HI) ^ 0243000)); /* Set proper exponent */ + return dfad(d, dw10zero, 0); /* Normalize and return */ +} + +/* C simop: (double)(unsigned)w */ + +dw10_t op10udflt(register w10_t w) +{ + register dw10_t d; + d.HI = w; + op10m_setz(d.LO); + d = op10lshc(d, -9); /* Make room for exponent */ + RSHIFTM(d.LO, 1); /* Get out of way of sign bit */ + LHSET(d.HI, (LHGET(d.HI) ^ 0244000)); /* Set proper exponent */ + return dfad(d, dw10zero, 0); /* Normalize and return */ +} + +/* C simop: (float)(double)d */ + +w10_t op10dsngl(register dw10_t d) +{ + int sign; + if (sign = wskipl(d.HI)) + op10m_dmovn(d); + if (LHGET(d.LO) & (HSIGN>>1)) { + if (RHGET(d.HI) & 01) { + w10_t one; + LRHSET(one, (LHGET(d.HI) & 0777000), 01); + d.HI = ffadr(d.HI, one, FADF_NORND); /* No rounding */ + } else + op10m_iori(d.HI, 1); + } + if (sign) + op10m_movn(d.HI); + return d.HI; +} + +#endif /* OP10_KCCOPS */ diff --git a/src/kn10ops.h b/src/kn10ops.h new file mode 100644 index 0000000..fc1ff8f --- /dev/null +++ b/src/kn10ops.h @@ -0,0 +1,721 @@ +/* KN10OPS.H - PDP-10 arithmetic operations for KLH10 +*/ +/* $Id: kn10ops.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: kn10ops.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* +** (Header file for routines in KN10OPS.C, plus inline macros) +** KN10OPS is primarily intended for use by the KLH10 PDP-10 emulator, but +** can also be used by KCC (PDP-10 C compiler) or other programs which want +** to emulate PDP-10 arithmetic operations. Note there are a number of +** artifacts defined here for KCC which aren't needed by the KLH10. +*/ + +#ifndef KN10OPS_INCLUDED +#define KN10OPS_INCLUDED 1 + +#ifdef RCSID + RCSID(kn10ops_h,"$Id: kn10ops.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +#include "word10.h" /* Define architecture of PDP-10 word */ + + +#ifdef OP10_PCFSET /* If we'll need flags, define them here? */ + /* This should be cleaned up later... */ +/* PDP-10 processor flags (not all used by all models). +** These defs are the left half bit values of actual PDP-10 PC flags. +*/ +#define PCF_ARO 0400000 /* Arithmetic Overflow (or Prev Ctxt Public) */ +#define PCF_CR0 0200000 /* Carry 0 - Carry out of bit 0 */ +#define PCF_CR1 0100000 /* Carry 1 - Carry out of bit 1 */ +#define PCF_FOV 040000 /* Floating Overflow */ +#if 0 /* Not used by arithmetic ops */ +# define PCF_FPD 020000 /* First Part Done */ +# define PCF_USR 010000 /* User Mode */ +# define PCF_UIO 04000 /* User In-Out (or Prev Ctxt User) */ +# define PCF_PUB 02000 /* Public Mode */ +# define PCF_INH 01000 /* Addr Failure Inhibit */ +#endif +#define PCF_TR2 0400 /* Trap 2 (PDL overflow) */ +#define PCF_TR1 0200 /* Trap 1 (Arith overflow) */ +#define PCF_FXU 0100 /* Floating Exponent Underflow */ +#define PCF_DIV 040 /* No Divide */ + +#endif /* OP10_PCFSET */ + +/* External data constants from KN10OPS */ +extern w10_t w10zero; /* Word of all zeros (0) */ +extern w10_t w10ones; /* Word of all ones (-1) */ +extern w10_t w10one; /* Word of just one (1) */ +extern dw10_t dw10zero; /* Double of all zeros (0.0) */ + +/* Declare functions which implement all PDP-10 instruction operations. */ + +#if !WORD10_USENAT /* Only needed if not using native PDP-10 code */ + +/* External symbol redefs to avoid name clashes (per 6-char monocase limit) */ +#if CENV_CPU_PDP10 /* If compiling on a PDP-10 */ +# define op10movn o1movn +# define op10movm o1movm +# define op10lshc o1dlsh +# define op10ashc o1dash +# define op10rot o1rot +# define op10rotc o1drot +# define op10ffo o1ffo +# define op10fad o1fad +# define op10fsb o1fsb +# define op10fmp o1fmp +# define op10fdv o1fdv +# define op10fadr o1fadr +# define op10fsbr o1fsbr +# define op10fmpr o1fmpr +# define op10fdvr o1fdvr +# define op10fadl o1fadl +# define op10fsbl o1fsbl +# define op10fmpl o1fmpl +# define op10fdvl o1fdvl +# define op10fsc o1fsc +# define op10fix o1fix +# define op10fixr o1fixr +# define op10fltr o1fltr +# define op10dsub o1dsub +# define op10dmul o1dmul +# define op10dfad o1dfad +# define op10dfsb o1dfsb +# define op10dfmp o1dfmp +# define op10dfdv o1dfdv +# define op10dinc o1dinc +# define op10dflt o1dflt +# define op10xmul o1xmul +# define op10uidiv o1udiv +# define op10dfn o1dfn +# define op10ufa o1ufa + +# define op10gfad o1gfad +# define op10gfsb o1gfsb +# define op10gfmp o1gfmp +# define op10gfdv o1gfdv +# define op10gfsc o1gfsc +# define op10gdble o1gdbl +# define op10dgfltr o1dgfl +# define op10gfltr o1gflt + +# define op10xfdv o1xfdv +# define op10xfix o1xfix +# define op10xdiv o1xdiv +# define op10xidiv o1xidi +# define op10xgfix o1xgfi +# define op10xgdfix o1xgdf +# define op10xgsngl o1xgsn + + +#endif /* CENV_CPU_PDP10 */ + +/* External routines */ + +#if 0 +extern w10_t op10stow(int32); +#endif + +#if OP10_KCCOPS /* C simulated ops */ +extern int + op10cmp(w10_t, w10_t), + op10ucmp(w10_t, w10_t), + op10dcmp(dw10_t, dw10_t), + op10tst(w10_t), + op10dtst(dw10_t); +extern w10_t + op10uimul(w10_t, w10_t), + op10uflt(w10_t), + op10dfix(dw10_t), + op10dsngl(dw10_t); +extern dw10_t + op10dneg(dw10_t), + op10uidiv(w10_t, w10_t), + op10dflt(w10_t), + op10udflt(w10_t); +#endif /* OP10_KCCOPS */ + +extern int32 + op10wtos(w10_t); + +extern w10_t + op10utow(uint32), + op10add(w10_t, w10_t), + op10sub(w10_t, w10_t), + op10imul(w10_t, w10_t), + op10and(w10_t, w10_t), + op10ior(w10_t, w10_t), + op10xor(w10_t, w10_t), + op10setcm(w10_t), + op10lsh(w10_t, h10_t), + op10ash(w10_t, h10_t), + op10rot(w10_t, h10_t), + op10movn(w10_t), + op10movm(w10_t), + op10inc(w10_t), + op10fad(w10_t, w10_t), + op10fsb(w10_t, w10_t), + op10fmp(w10_t, w10_t), + op10fdv(w10_t, w10_t), + op10fadr(w10_t, w10_t), + op10fsbr(w10_t, w10_t), + op10fmpr(w10_t, w10_t), + op10fdvr(w10_t, w10_t), + op10fsc(w10_t, h10_t), + op10fltr(w10_t), + op10fix(w10_t), + op10fixr(w10_t), + op10ufa(w10_t, w10_t); /* KA10 op */ + +extern dw10_t + op10mul(w10_t, w10_t), + op10xmul(w10_t, w10_t), + op10div(dw10_t, w10_t), + op10idiv(w10_t, w10_t), + op10lshc(dw10_t, h10_t), + op10ashc(dw10_t, h10_t), + op10rotc(dw10_t, h10_t), + op10circ(dw10_t, h10_t), /* ITS instr only */ + op10dadd(dw10_t, dw10_t), + op10dsub(dw10_t, dw10_t), + op10fadl(w10_t, w10_t), /* KA10 */ + op10fsbl(w10_t, w10_t), /* KA10 */ + op10fmpl(w10_t, w10_t), /* KA10 */ + op10fdvl(dw10_t, w10_t), /* KA10 */ + op10dfad(dw10_t, dw10_t), + op10dfsb(dw10_t, dw10_t), + op10dfmp(dw10_t, dw10_t), + op10dfdv(dw10_t, dw10_t), + op10gfad(dw10_t, dw10_t), /* KL */ + op10gfsb(dw10_t, dw10_t), /* KL */ + op10gfmp(dw10_t, dw10_t), /* KL */ + op10gfdv(dw10_t, dw10_t), /* KL */ + op10gfsc(dw10_t, h10_t), /* KL */ + op10gdble(w10_t), /* KL */ + op10dgfltr(dw10_t), /* KL */ + op10gfltr(w10_t), /* KL */ + op10dmovn(dw10_t), + op10dfn(dw10_t), /* KA10 */ + op10dinc(dw10_t), /* C simop but used by optest */ + op10fdble(w10_t); /* C simop but used internally */ + +extern qw10_t /* Return quadwords */ + op10dmul(dw10_t, dw10_t), + op10ddiv(qw10_t, dw10_t); + +extern int /* Operations that can abort */ + op10xfdv(w10_t *, w10_t, int), + op10xfix(w10_t *, int), + op10xdiv(dw10_t *, w10_t), + op10xidiv(dw10_t *, w10_t, w10_t), + op10xgfix(dw10_t *, int), + op10xgdfix(dw10_t *, int), + op10xgsngl(dw10_t *); + +extern int /* Miscellaneous */ + op10ffo(w10_t); + +#endif /* !WORD10_USENAT */ + +/* Define op10m_* macros for optimizing in-line coding if possible. +** All of the test macros such as op10m_signtst, skip*, cam*, trn* +** must return a LOGICAL true or false depending on the condition +** being tested for. This ensures that their value can be assigned +** to an integer boolean variable, without compromising their efficiency +** when used in C conditional expressions. +** The other op10m_* macros modify their first arg (which must be an lvalue!) +** IN PLACE; no value is returned and no flags are set. +** +** HOWEVER... +** The few op10mf_* macros that exist *DO* set the flags! +** Their actions are otherwise identical to their op10m_ counterparts. +*/ + +#if WORD10_USEHWD || WORD10_USEGCCSPARC || WORD10_USEHUN + /* Common internal macros to hack halfwords, which are accessed + ** using LHVAL and RHVAL -- which must be lvalues! + */ +# define OP10H_MOVN(w) (LHVAL(w) = (RHVAL(w) = (-RHVAL(w) & H10MASK)) \ + ? (LHVAL(w) ^ H10MASK) : (-LHVAL(w) & H10MASK) ) + +/* The following 4 won't work unless halfwords are stored in >=19 bits */ +# define OP10H_ADD(a,b) ( \ + (((RHVAL(a) += RHVAL(b)) & ~H10MASK) \ + ? ((RHVAL(a) &= H10MASK), ++LHVAL(a)) : 0), \ + (LHVAL(a) = (LHVAL(a) + LHVAL(b)) & H10MASK) ) +# define OP10H_SUB(a,b) ( \ + (((RHVAL(a) -= RHVAL(b)) & ~H10MASK) \ + ? ((RHVAL(a) &= H10MASK), --LHVAL(a)) : 0), \ + (LHVAL(a) = (LHVAL(a) - LHVAL(b)) & H10MASK) ) +# define OP10H_ADDI(w,r) ( \ + ((RHVAL(w)=(RHVAL(w)+(r))) & ~H10MASK) \ + ? ((RHVAL(w) &= H10MASK), LHVAL(w) = (LHVAL(w)+1)&H10MASK) \ + : 0 ) +# define OP10H_SUBI(w,r) ( \ + ((RHVAL(w)=(RHVAL(w)-(r))) & ~H10MASK) \ + ? ((RHVAL(w) &= H10MASK), LHVAL(w) = (LHVAL(w)-1)&H10MASK) \ + : 0 ) +# define OP10H_INC(w) ( \ + (++RHVAL(w) & ~H10MASK) \ + ? ((RHVAL(w) = 0), LHVAL(w) = (LHVAL(w)+1)&H10MASK) \ + : 0 ) +# define OP10H_DEC(w) ( \ + RHVAL(w) ? --RHVAL(w) \ + : ((RHVAL(w) = H10MASK), LHVAL(w) = (LHVAL(w)-1)&H10MASK) ) + +# define OP10H_LSHIFT(w,s) \ + ((s) < H10BITS \ + ? ( (LHVAL(w) = ((LHVAL(w) << (s))&H10MASK) | (RHVAL(w)>>(H10BITS-(s)))) \ + , (RHVAL(w) = (RHVAL(w) << (s)) & H10MASK) )\ + : ( (((s) > W10BITS) ? (LHVAL(w) = 0) \ + : (LHVAL(w) = (RHVAL(w)<<((s)-H10BITS)) & H10MASK) )\ + , (RHVAL(w) = 0) ) ) +# define OP10H_RSHIFT(w,s) \ + ((s) < H10BITS \ + ? ( (RHVAL(w) = (RHVAL(w) >> (s)) | ((LHVAL(w)<<(H10BITS-(s)))&H10MASK) )\ + , (LHVAL(w) >>= (s)) )\ + : ( (((s) > W10BITS) ? (RHVAL(w) = 0) \ + : (RHVAL(w) = LHVAL(w)>>((s)-H10BITS)) )\ + , (LHVAL(w) = 0) ) ) + +/* Flag setting for ops: +** AOJ/AOS SOJ/SOS +** -1 => 0 cry0+cry1 0 => -1 none +** +max => setz cry1+ovfl+trap1 setz => +max cry0+ovfl+trap1 +** else none else cry0+cry1 +*/ +# define OP10HF_INC(w) ( \ + (++RHVAL(w) & H10MASK) ? 0 \ + : ((RHVAL(w) = 0), ((++LHVAL(w) & H10MAGS) ? 0 \ + : ((LHVAL(w) &= H10MASK) ? OP10_PCFSET(PCF_CR1|PCF_ARO|PCF_TR1) \ + : OP10_PCFSET(PCF_CR0|PCF_CR1) \ + ))) ) +# define OP10HF_DEC(w) ( \ + (RHVAL(w) || (LHVAL(w)&H10MAGS)) \ + ? (OP10H_DEC(w), OP10_PCFSET(PCF_CR0|PCF_CR1)) \ + : ((RHVAL(w) = H10MASK), (((LHVAL(w) ^= H10MASK) & H10SIGN) ? 0 \ + : OP10_PCFSET(PCF_CR0|PCF_ARO|PCF_TR1) \ + )) ) +# define OP10HF_MOVN(w) ( \ + (RHVAL(w) = (-RHVAL(w) & H10MASK)) \ + ? (LHVAL(w) ^= H10MASK) \ + : ( ((LHVAL(w) = (-LHVAL(w) & H10MASK)) & H10MAGS) ? 0 \ + : ((LHVAL(w)&H10SIGN) \ + ? (OP10_PCFSET(PCF_CR1|PCF_ARO|PCF_TR1), 0) \ + : (OP10_PCFSET(PCF_CR0|PCF_CR1), 0) ) \ + ) ) + +#endif /* HWD || GCCSPARC || HUN */ + + +#if WORD10_USEHWD + +/* Simple word-value stuff */ +#define op10m_signtst(w) (((w).lh&H10SIGN)!=0) /* Test for sign bit */ +#define op10m_magstst(w) ((w).rh || ((w).lh&H10MAGS)) /* Test for mag bits */ +#define op10m_signclr(w) ((w).lh&=H10MAGS) /* Clear sign (and up) */ +#define op10m_signset(w) ((w).lh|=H10SIGN) /* Set sign */ + +/* Unary ops */ +#define op10m_skipn(w) ((w).lh || (w).rh) +#define op10m_setcm(w) (((w).lh ^= H10MASK), ((w).rh ^= H10MASK)) +#define op10m_setz(w) ((w).lh = 0, (w).rh = 0) +#define op10m_seto(w) ((w).lh = H10MASK, (w).rh = H10MASK) + +/* Binary ops, no flags */ +#define op10m_and(a,b) ((a).lh &= (b).lh, (a).rh &= (b).rh) +#define op10m_ior(a,b) ((a).lh |= (b).lh, (a).rh |= (b).rh) +#define op10m_xor(a,b) ((a).lh ^= (b).lh, (a).rh ^= (b).rh) +#define op10m_andcm(a,b) ((a).lh &= ~(b).lh, (a).rh &= ~(b).rh) +#define op10m_tdnn(a,b) (((a).lh & (b).lh) || ((a).rh & (b).rh)) +#define op10m_camn(a,b) ((a).lh != (b).lh || (a).rh != (b).rh) +#define op10m_ucmpl(a,b) ((a).lh < (b).lh \ + || ((a).lh==(b).lh && (a).rh < (b).rh)) +#define op10m_caml(a,b) ((((a).lh^(b).lh)&H10SIGN) \ + ? op10m_ucmpl(b,a) : op10m_ucmpl(a,b)) +#define op10m_lshift(w,s) OP10H_LSHIFT(w,s) +#define op10m_rshift(w,s) OP10H_RSHIFT(w,s) +#define op10m_tlo(w,h) ((w).lh |= (h)) +#define op10m_tlz(w,h) ((w).lh &= ~(h)) +#define op10m_tlnn(w,h) (((w).lh & (h)) != 0) +#define op10m_tro(w,h) ((w).rh |= (h)) +#define op10m_trz(w,h) ((w).rh &= ~(h)) +#define op10m_trnn(w,h) (((w).rh & (h)) != 0) +#define op10m_txnn(w,l,r) (((w).lh & (l)) || ((w).rh & (r))) +#define op10m_cail(w,r) ((w).lh ? op10m_signtst(w) : ((w).rh < (r))) +#define op10m_caile(w,r) ((w).lh ? op10m_signtst(w) : ((w).rh <= (r))) +#define op10m_cain(w,r) ((w).rh != (r) || (w).lh) + +/* Arithmetic ops, special flagless versions */ +#define op10m_movn(w) OP10H_MOVN(w) +#define op10m_add(a,b) OP10H_ADD(a,b) +#define op10m_sub(a,b) OP10H_SUB(a,b) +#define op10m_addi(w,r) OP10H_ADDI(w,r) +#define op10m_subi(w,r) OP10H_SUBI(w,r) +#define op10m_inc(w) OP10H_INC(w) +#define op10m_dec(w) OP10H_DEC(w) + +/* Arithmetic ops, flag-setting versions! */ +# ifdef OP10_PCFSET +# define op10mf_inc(w) OP10HF_INC(w) +# define op10mf_dec(w) OP10HF_DEC(w) +# define op10mf_movn(w) OP10HF_MOVN(w) +# endif /* OP10_PCFSET */ + +/* endif WORD10_USEHWD */ + +#elif WORD10_USEINT + +/* Simple word-value stuff */ +#define op10m_signtst(w) ((XWDVAL(w)&W10SIGN)!=0) /* Test for sign bit */ +#define op10m_magstst(w) ((XWDVAL(w)&W10MAGS)!=0) /* Test for mag bits */ +#define op10m_signclr(w) (XWDVAL(w)&=W10MAGS) /* Clear sign */ +#define op10m_signset(w) (XWDVAL(w)|=W10SIGN) /* Set sign */ + +/* Unary ops */ +#define op10m_skipn(w) (XWDVAL(w) != 0) +#define op10m_setcm(w) (XWDVAL(w) ^= W10MASK) +#define op10m_setz(w) (XWDVAL(w) = 0) +#define op10m_seto(w) (XWDVAL(w) = W10MASK) + +/* Binary ops, no flags */ +#define op10m_and(a,b) (XWDVAL(a) &= XWDVAL(b)) +#define op10m_ior(a,b) (XWDVAL(a) |= XWDVAL(b)) +#define op10m_xor(a,b) (XWDVAL(a) ^= XWDVAL(b)) +#define op10m_andcm(a,b) (XWDVAL(a) &= ~XWDVAL(b)) +#define op10m_tdnn(a,b) ((XWDVAL(a) & XWDVAL(b)) != 0) +#define op10m_camn(a,b) (XWDVAL(a) != XWDVAL(b)) +#define op10m_ucmpl(a,b) (XWDVAL(a) < XWDVAL(b)) +#define op10m_caml(a,b) (((XWDVAL(a) ^ XWDVAL(b))&W10SIGN) \ + ? op10m_ucmpl(b,a) : op10m_ucmpl(a,b)) +#define op10m_lshift(w,s) (XWDVAL(w) = \ + ((s) >= W10BITS) ? 0 : ((XWDVAL(w) << (s)) & W10MASK)) +#define op10m_rshift(w,s) (XWDVAL(w) = \ + ((s) >= W10BITS) ? 0 : ((XWDVAL(w) >> (s)) & W10MASK)) +#define op10m_tlo(w,l) (XWDVAL(w) |= ((w10uint_t)(l)< (r)) : (XWDVAL(w) < (r))) +#define op10m_caile(w,r) (op10m_signtst(w) \ + ? (XWDVAL(w) >= (r)) : (XWDVAL(w) <= (r))) +#define op10m_cain(w,r) (XWDVAL(w) != (r)) + +/* Arithmetic ops, special flagless versions */ +#define op10m_movn(a) (XWDVAL(a) = (-XWDVAL(a)) & W10MASK) +#define op10m_add(a,b) (XWDVAL(a) = (XWDVAL(a)+XWDVAL(b)) & W10MASK) +#define op10m_sub(a,b) (XWDVAL(a) = (XWDVAL(a)-XWDVAL(b)) & W10MASK) +#define op10m_addi(w,r) (XWDVAL(w) = (XWDVAL(w)+(r)) & W10MASK) +#define op10m_subi(w,r) (XWDVAL(w) = (XWDVAL(w)-(r)) & W10MASK) +#define op10m_inc(w) (XWDVAL(w) = (XWDVAL(w)+1) & W10MASK) +#define op10m_dec(w) (XWDVAL(w) = (XWDVAL(w)-1) & W10MASK) + +/* Arithmetic ops, flag-setting versions! */ +# ifdef OP10_PCFSET +# define op10mf_inc(w) (op10m_inc(w), (op10m_magstst(w) ? 0 \ + : (op10m_signtst(w) ? OP10_PCFSET(PCF_CR1|PCF_ARO|PCF_TR1) \ + : OP10_PCFSET(PCF_CR0|PCF_CR1) ))) +# define op10mf_dec(w) (op10m_magstst(w) \ + ? (op10m_dec(w), OP10_PCFSET(PCF_CR0|PCF_CR1)) \ + : (op10m_setcm(w), (op10m_signtst(w) ? 0 \ + : OP10_PCFSET(PCF_CR0|PCF_ARO|PCF_TR1) ))) +# define op10mf_movn(w) (op10m_magstst(w) \ + ? op10m_movn(w) \ + : (op10m_signtst(w) \ + ? OP10_PCFSET(PCF_CR1|PCF_ARO|PCF_TR1) \ + : OP10_PCFSET(PCF_CR0|PCF_CR1) )) +# endif /* OP10_PCFSET */ + +/* endif WORD10_USEINT */ + +#elif WORD10_USEGCCSPARC + +/* Simple word-value stuff */ +#define op10m_signtst(w) ((LHGET(w)&H10SIGN)!=0) /* Test for sign bit */ +#define op10m_magstst(w) (RHGET(w)||(LHGET(w)&H10MAGS)) /* Test for mag bits */ +#define op10m_signclr(w) ((w) = w10_signclr(w)) /* Clear sign */ +static inline w10_t w10_signclr(w10_t wi) + { register w10union_t w; w.i = wi; + w.uwd.lh &= ~H10SIGN; + return w.i; + } +#define op10m_signset(w) op10m_tlo(w, H10SIGN) /* Set sign */ + +/* Unary ops */ +#define op10m_skipn(w) (XWDVAL(w) != 0) +#define op10m_setcm(w) (XWDVAL(w) ^= W10MASK) +#define op10m_setz(w) (XWDVAL(w) = 0) +#define op10m_seto(w) (XWDVAL(w) = W10MASK) + +/* Binary ops, no flags */ +#define op10m_and(a,b) (XWDVAL(a) &= XWDVAL(b)) +#define op10m_ior(a,b) (XWDVAL(a) |= XWDVAL(b)) +#define op10m_xor(a,b) (XWDVAL(a) ^= XWDVAL(b)) +#define op10m_andcm(a,b) (XWDVAL(a) &= ~XWDVAL(b)) +#define op10m_tdnn(a,b) ((XWDVAL(a) & XWDVAL(b)) != 0) +#define op10m_camn(a,b) (XWDVAL(a) != XWDVAL(b)) +#define op10m_ucmpl(a,b) (XWDVAL(a) < XWDVAL(b)) +#define op10m_caml(a,b) (((LHGET(a)^LHGET(b))&H10SIGN) \ + ? op10m_ucmpl(b,a) : op10m_ucmpl(a,b)) + +#define op10m_lshift(w,s) ((w) = w10_lshift(w, s)) +static inline w10_t w10_lshift(w10_t wi, h10_t s) + { register w10union_t w; w.i = wi; + OP10H_LSHIFT(w, s); + return w.i; + } +#define op10m_rshift(w,s) ((w) = w10_rshift(w, s)) +static inline w10_t w10_rshift(w10_t wi, h10_t s) + { register w10union_t w; w.i = wi; + OP10H_RSHIFT(w, s); + return w.i; + } + +#define op10m_tlo(w,l) ((w) = w10_tlo(w, l)) + static inline w10_t w10_tlo(w10_t wi, h10_t l) + { register w10union_t w; w.i = wi; w.uwd.lh |= l; return w.i; } +#define op10m_tlz(w,l) ((w) = w10_tlz(w, l)) + static inline w10_t w10_tlz(w10_t wi, h10_t l) + { register w10union_t w; w.i = wi; w.uwd.lh &= ~l; return w.i; } +#define op10m_tlnn(w,l) ((LHGET(w) & (l)) != 0) +#define op10m_tro(w,h) (XWDVAL(w) |= (h)) +#define op10m_trz(w,h) (XWDVAL(w) &= ~(h)) +#define op10m_trnn(w,h) ((XWDVAL(w) & (h)) != 0) +#define op10m_txnn(w,l,r) ((XWDVAL(w) & XWDIMM(l,r)) != 0) +#define op10m_cail(w,r) (LHGET(w) ? op10m_signtst(w) : (RHGET(w) < (r))) +#define op10m_caile(w,r) (LHGET(w) ? op10m_signtst(w) : (RHGET(w) <= (r))) +#define op10m_cain(w,r) (RHGET(w) != (r) || LHGET(w)) + +/* Arithmetic ops, special flagless versions */ +#define op10m_movn(w) ((w) = w10_movn(w)) + static inline w10_t w10_movn(w10_t wi) + { register w10union_t w; w.i = wi; OP10H_MOVN(w); return w.i; } +#define op10m_add(a,b) ((a) = w10_add(a,b)) + static inline w10_t w10_add(w10_t ai, w10_t bi) + { register w10union_t a, b; a.i = ai; b.i = bi; + OP10H_ADD(a,b); + return a.i; + } +#define op10m_sub(a,b) ((a) = w10_sub(a,b)) +static inline w10_t w10_sub(w10_t ai, w10_t bi) + { register w10union_t a, b; a.i = ai; b.i = bi; + OP10H_SUB(a,b); + return a.i; + } +#define op10m_addi(w,r) ((w) = w10_addi(w,r)) + static inline w10_t w10_addi(w10_t wi, h10_t r) + { register w10union_t w; w.i = wi; OP10H_ADDI(w,r); return w.i; } +#define op10m_subi(w,r) ((w) = w10_subi(w,r)) + static inline w10_t w10_subi(w10_t wi, h10_t r) + { register w10union_t w; w.i = wi; OP10H_SUBI(w,r); return w.i; } +#define op10m_inc(w) ((w) = w10_inc(w)) + static inline w10_t w10_inc(w10_t wi) + { register w10union_t w; w.i = wi; OP10H_INC(w); return w.i; } +#define op10m_dec(w) ((w) = w10_dec(w)) + static inline w10_t w10_dec(w10_t wi) + { register w10union_t w; w.i = wi; OP10H_DEC(w); return w.i; } + +/* Arithmetic ops, flag-setting versions! */ +# ifdef OP10_PCFSET +# define op10mf_inc(w) ((w) = w10f_inc(w)) + static inline w10_t w10f_inc(w10_t wi) + { register w10union_t w; w.i = wi; OP10HF_INC(w); return w.i; } +# define op10mf_dec(w) ((w) = w10f_dec(w)) + static inline w10_t w10f_dec(w10_t wi) + { register w10union_t w; w.i = wi; OP10HF_DEC(w); return w.i; } +# define op10mf_movn(w) ((w) = w10f_movn(w)) + static inline w10_t w10f_movn(w10_t wi) + { register w10union_t w; w.i = wi; OP10HF_MOVN(w); return w.i; } +# endif /* OP10_PCFSET */ + +/* endif WORD10_USEGCCSPARC */ + + +#elif WORD10_USEHUN + +/* Simple word-value stuff, same as USEINT */ +#define op10m_signtst(w) ((XWDVAL(w)&W10SIGN)!=0) /* Test for sign bit */ +#define op10m_magstst(w) ((XWDVAL(w)&W10MAGS)!=0) /* Test for mag bits */ +#define op10m_signclr(w) (XWDVAL(w)&=W10MAGS) /* Clear sign */ +#define op10m_signset(w) (XWDVAL(w)|=W10SIGN) /* Set sign */ + +/* Unary ops, same as USEINT */ +#define op10m_skipn(w) (XWDVAL(w) != 0) +#define op10m_setcm(w) (XWDVAL(w) ^= W10MASK) +#define op10m_setz(w) (XWDVAL(w) = 0) +#define op10m_seto(w) (XWDVAL(w) = W10MASK) + +/* Binary ops, no flags. Most same as USEINT, others same as USEHWD. */ +#define op10m_and(a,b) (XWDVAL(a) &= XWDVAL(b)) +#define op10m_ior(a,b) (XWDVAL(a) |= XWDVAL(b)) +#define op10m_xor(a,b) (XWDVAL(a) ^= XWDVAL(b)) +#define op10m_andcm(a,b) (XWDVAL(a) &= ~XWDVAL(b)) +#define op10m_tdnn(a,b) ((XWDVAL(a) & XWDVAL(b)) != 0) +#define op10m_camn(a,b) (XWDVAL(a) != XWDVAL(b)) +#define op10m_ucmpl(a,b) (XWDVAL(a) < XWDVAL(b)) +#define op10m_caml(a,b) (((XWDVAL(a) ^ XWDVAL(b))&W10SIGN) \ + ? op10m_ucmpl(b,a) : op10m_ucmpl(a,b)) +#define op10m_lshift(w,s) OP10H_LSHIFT(w,s) +#define op10m_rshift(w,s) OP10H_RSHIFT(w,s) +#define op10m_tlo(w,l) (LHVAL(w) |= (l)) +#define op10m_tlz(w,l) (LHVAL(w) &= ~(l)) +#define op10m_tlnn(w,l) ((LHGET(w) & (l)) != 0) +#define op10m_tro(w,h) (XWDVAL(w) |= (h)) +#define op10m_trz(w,h) (XWDVAL(w) &= ~(h)) +#define op10m_trnn(w,h) ((XWDVAL(w) & (h)) != 0) +#define op10m_txnn(w,l,r) ((XWDVAL(w) & XWDIMM(l,r)) != 0) + +#define op10m_cail(w,r) (op10m_signtst(w) \ + ? (XWDVAL(w) > (r)) : (XWDVAL(w) < (r))) +#define op10m_caile(w,r) (op10m_signtst(w) \ + ? (XWDVAL(w) >= (r)) : (XWDVAL(w) <= (r))) +#define op10m_cain(w,r) (XWDVAL(w) != (r)) + +/* Arithmetic ops, special flagless versions. Same as USEHWD. */ +#define op10m_movn(w) OP10H_MOVN(w) +#define op10m_add(a,b) OP10H_ADD(a,b) +#define op10m_sub(a,b) OP10H_SUB(a,b) +#define op10m_addi(w,r) OP10H_ADDI(w,r) +#define op10m_subi(w,r) OP10H_SUBI(w,r) +#define op10m_inc(w) OP10H_INC(w) +#define op10m_dec(w) OP10H_DEC(w) + +/* Arithmetic ops, flag-setting versions! Same as USEHWD.*/ +# ifdef OP10_PCFSET +# define op10mf_inc(w) OP10HF_INC(w) +# define op10mf_dec(w) OP10HF_DEC(w) +# define op10mf_movn(w) OP10HF_MOVN(w) +# endif /* OP10_PCFSET */ + +#endif /* WORD10_USEHUN */ + + +/* Derive remaining op10m macros in model-independent way */ + +#define op10m_iori(w,h) op10m_tro(w,h) +#define op10m_andcmi(w,h) op10m_trz(w,h) + +#define op10m_caie(w,r) !op10m_cain(w,r) /* (w == i) */ +#define op10m_caig(w,r) !op10m_caile(w,r) /* (w > i) */ +#define op10m_caige(w,r) !op10m_cail(w,r) /* (w >= i) */ +#ifndef op10m_skipl +# define op10m_skipl(w) op10m_signtst(w) /* (w < 0) */ +#endif +#ifndef op10m_skipg /* (w > 0) */ +# define op10m_skipg(w) (!op10m_skipl(w) && op10m_skipn(w)) +#endif +#define op10m_skipe(w) !op10m_skipn(w) /* (w == 0) */ +#define op10m_skiple(w) !op10m_skipg(w) /* (w <= 0) */ +#define op10m_skipge(w) !op10m_skipl(w) /* (w >= 0) */ +#define op10m_camg(a,b) op10m_caml(b,a) /* (a > b) */ +#define op10m_came(a,b) !op10m_camn(a,b) /* (a == b) */ +#define op10m_camle(a,b) !op10m_camg(a,b) /* (a <= b) */ +#define op10m_camge(a,b) !op10m_caml(a,b) /* (a >= b) */ + +/* Model-independent op10mf_ flag-setting macros */ + +#ifdef OP10_PCFSET +# define op10mf_movm(w) (op10m_signtst(w) ? op10mf_movn(w) : 0) +# define op10mf_addi(w,r) \ + (op10m_signtst(w) \ + ? (op10m_addi(w,r), (op10m_signtst(w) \ + ? 0 : OP10_PCFSET(PCF_CR0|PCF_CR1)) ) \ + : (op10m_addi(w,r), (op10m_signtst(w) \ + ? OP10_PCFSET(PCF_CR1|PCF_ARO|PCF_TR1) : 0) )) +# define op10mf_subi(w,r) \ + (op10m_signtst(w) \ + ? (op10m_subi(w,r), (op10m_signtst(w) \ + ? OP10_PCFSET(PCF_CR0|PCF_CR1) \ + : OP10_PCFSET(PCF_CR0|PCF_ARO|PCF_TR1))) \ + : (op10m_subi(w,r), (op10m_signtst(w) \ + ? 0 : OP10_PCFSET(PCF_CR0|PCF_CR1)) )) + +# define op10mf_dmovn(d) ( \ + op10m_movn((d).w[1]), /* Negate low word */\ + op10m_signclr((d).w[1]), /* Clear its sign bit */\ + (op10m_skipn((d).w[1]) /* Anything left? */\ + ? op10m_setcm((d).w[0]) /* Yep, just complement high */\ + : op10mf_movn((d).w[0]) /* Low clear, carry into high, with flags */\ + )) + +/* Actually, the op10mf_inc and op10mf_dec macros could be model-independent +** as well (see the defs for USEINT) but the halfword versions are +** slightly more efficient. +*/ +#endif /* OP10_PCFSET */ + +/* Model-independent double-word macros */ + +#define op10m_dsetz(d) (op10m_setz((d).w[0]), op10m_setz((d).w[1])) + +#define op10m_dmovn(d) ( \ + op10m_movn((d).w[1]), /* Negate low word */\ + op10m_signclr((d).w[1]), /* Clear its sign bit */\ + (op10m_skipn((d).w[1]) /* Anything left? */\ + ? op10m_setcm((d).w[0]) /* Yep, no carry, just complement high */\ + : op10m_movn((d).w[0]) /* Low clear, carry into high, no flags */\ + )) + +/* Special double-word fixed-point negation, which sets low sign to high's. */ +#define op10m_dneg(d) ( \ + op10m_dmovn(d), /* Negate double, always clears low sign */\ + (op10m_signtst((d).w[0]) /* Is high sign set? */\ + ? (op10m_signset((d).w[1]), 0) /* Yep, set low sign */\ + : 0 /* Bogosity to keep C type-checking happy */\ + )) + +/* Special unsigned add of word to double-word. Leaves low sign clear. */ +#define op10m_udaddw(da,b) (\ + op10m_signclr((da).w[1]), /* Must ignore signs of low words */\ + op10m_signclr(b), \ + op10m_add((da).w[1], (b)), /* Add low B to low A */\ + (op10m_skipl((da).w[1]) ? /* If sign set, clear and carry to high */\ + (op10m_signclr((da).w[1]), op10m_inc((da).w[0]), 0) \ + : 0)) + +/* Special unsigned compare of double-words */ +#define op10m_udcmpl(da,db) \ + (op10m_ucmpl((da).w[0], (db).w[0]) \ + || (op10m_came((da).w[0], (db).w[0]) \ + && op10m_ucmpl((da).w[1], (db).w[1]) ) ) + + +/* Special restricted left-shift of double-word fix value. +** Semi-arithmetic shift, ignores low-word sign +** but shifts into high-word sign. +** Restricted to shift of 1-17 bits. Always clears low-word sign! +*/ +#define op10m_ashcleft(d,s) ( \ + op10m_lshift((d).w[0], s), \ + op10m_iori((d).w[0], \ + ((LHGET((d).w[1])>>((H10BITS-1)-(s))) & (((h10_t)1<<(s))-1))), \ + op10m_lshift((d).w[1], s), \ + op10m_signclr((d).w[1]) ) + + +#endif /* ifndef KN10OPS_INCLUDED */ diff --git a/src/kn10pag.c b/src/kn10pag.c new file mode 100644 index 0000000..ea4b64e --- /dev/null +++ b/src/kn10pag.c @@ -0,0 +1,1961 @@ +/* KN10PAG.C - Main Processor: Pager +*/ +/* $Id: kn10pag.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: kn10pag.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#include + +#include "klh10.h" +#include "osdsup.h" +#include "kn10def.h" +#include "kn10ops.h" + +#ifdef RCSID + RCSID(kn10pag_c,"$Id: kn10pag.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Exported functions - see kn10pag.h */ + +/* Imported functions */ +extern void pishow(FILE *f); +extern void pcfshow(FILE *f, h10_t flags); +extern void insprint(FILE *, int); + +extern void pxct_undo(void); /* KN10CPU stuff needed for page fail trap */ +extern void trap_undo(void); +#if KLH10_ITS_1PROC +extern void a1pr_undo(void); +#elif KLH10_CPU_KI || KLH10_CPU_KL +extern void afi_undo(void); +#endif + +/* Local Pre-declarations */ +static void acblk_set(unsigned, unsigned); +static void pag_enable(int); +static void pag_clear(void); +static void pag_mapclr(pment_t *); +static void pag_segclr(pment_t *); +static void pag_nxmfail(paddr_t, pment_t, char *); + +/* Pager code */ +/* + As implemented here, the pager maintains its own internal map +tables which are used for all virtual memory references (non-virtual +ones go directly to physical memory). + + There are two map tables, one for EXEC mode mapping and the other +for USER mode. There is one map entry for each possible page in the virtual +address space. + + For the ITS pager this is 256 entries, further separated +into "low" and "high" halves of 128 entries each, so that each half can +be changed independently of the other. + + When a page map is first set up, just the location of the +entries in the PDP-10 memory is noted, and the internal map entries are +cleared. Any reference which finds an invalid (clear) entry will first +trap to the pager, which attempts to load the corresponding entry from +the PDP-10 memory. This is called a "page refill". If found and +valid, the entry is set internally and the reference allowed to +proceed. If still invalid, a page fault trap happens. + + Whenever the documentation talks about an instruction +"resetting the page table", for the emulator this means that the +relevant internal map entries are cleared. (There is no cache, thus +"clearing the cache" is a no-op.) + + Currently there is actually a third internal page map, which +merely mirrors physical memory and is used as the mapping table before +paging is turned on. This table never changes. + +*/ + +/* PAG_INIT - Initialize the paging system. +*/ +void +pag_init(void) +{ + register int i; + + /* Initialize externally visible registers */ +#if KLH10_CPU_KS + LRHSET(cpu.mr_ebr, 0, + (KLH10_PAG_KL ? EBR_T20 : 0)); /* Set flag if using T20 (KL) paging */ +#else + LRHSET(cpu.mr_ebr, 0, 0); /* KL diags expect 0?!? */ +#endif + +#if KLH10_CPU_KL + LRHSET(cpu.mr_ubr, UBR_SETACB | UBR_SETPCS | UBR_SET, 0); +#elif KLH10_CPU_KS + LRHSET(cpu.mr_ubr, UBR_SETACB | UBR_SET, 0); /* Bits 0&2 always set */ +#endif + +#if KLH10_JPC + cpu.mr_ujpc = cpu.mr_ejpc = cpu.mr_jpc = 0; +#endif +#if KLH10_PAG_ITS + cpu.pag.pr_dbr1 = cpu.pag.pr_dbr2 = /* DBR regs */ + cpu.pag.pr_dbr3 = cpu.pag.pr_dbr4 = 0; + cpu.pag.pr_quant = /* Misc stuff */ + cpu.pag.pr_ujpc = cpu.pag.pr_ejpc = 0; +#elif KLH10_PAG_KL +# if KLH10_CPU_KS + cpu.pag.pr_spb = cpu.pag.pr_csb = 0; /* Base addresses */ +# elif KLH10_CPU_KL + op10m_setz(PAG_PR_SPB); + op10m_setz(PAG_PR_CSB); +# endif + op10m_setz(PAG_PR_CSTMWD); /* Word values */ + op10m_setz(PAG_PR_PURWD); + + cpu.pag.pr_physnxm = (paddr_t)PAG_MAXPHYSPGS << PAG_BITS; +#endif /* KLH10_PAG_KL */ + +#if KLH10_CPU_KL + cpu.pag.pr_pcs = cpu.pag.pr_pcsf = 0; + cpu.pag.pr_era = 0; +#endif + + /* Do AC block initialization. */ + cpu.mr_acbcur = 0; /* Specify current AC block */ + acblk_set(0, 0); /* Set up cur & prev blocks */ + + /* Set up physical memory "map" */ + for (i = 0; i < PAG_MAXVIRTPGS; ++i) + pr_pmap[i] = (VMF_READ|VMF_WRITE) | i; + pag_clear(); /* Clear Exec and User maps */ + + pag_enable(0); /* Ensure paging is off internally */ +} + +/* ACBLK_SET - Subroutine to set current and previous AC blocks +** Always keeps the current block ACs in cpu.acs for faster reference. +*/ +static void +acblk_set(register unsigned int new, + register unsigned int old) +{ + if (new >= ACBLKS_N || old >= ACBLKS_N) + panic("acblk_set: ac blk # out of range: %d or %d", new, old); + if (cpu.mr_acbcur != new) { + /* Put current ACs back in shadow block, then load from new block */ + *(acblk_t *)cpu.acblks[cpu.mr_acbcur] = cpu.acs; + cpu.acs = *(acblk_t *)cpu.acblks[new]; /* Do structure copy! */ + cpu.mr_acbcur = new; /* Remember new AC block for next call */ + } + acmap_set(&cpu.acs.ac[0], /* Set up new mappings */ + ((old == new) ? &cpu.acs.ac[0] : cpu.acblks[old])); +} + +/* PAG_ENABLE - Enable or disable paging. +** Callers must also clear the cache, page map, and KLH10 PC cache if any, +** by invoking pag_clear() before or after this function. +*/ +static void +pag_enable(int onf) +{ + + /* Originally it wasn't clear whether the EPT, UPT, and AC block selection + ** were in effect even when paging is off. + ** However, this appears to be the case for the KL, + ** and reportedly the KS as well. + */ +#if 0 /* KLH10_CPU_KS */ + if (onf) { /* Turning paging/traps on or off? */ +#else + if (1) { /* ALWAYS set EPT/UPT and AC block selections */ +#endif + /* Set word addr of EPT and UPT so we start using them */ + cpu.mr_ebraddr = (paddr_t)(RHGET(cpu.mr_ebr) & EBR_BASEPAG) << 9; + cpu.mr_ubraddr = +#if KLH10_PAG_ITS + ((paddr_t)(LHGET(cpu.mr_ubr) & UBR_BASELH) << 18) + | RHGET(cpu.mr_ubr); +#else /* DEC */ + pag_pgtopa(RHGET(cpu.mr_ubr) & UBR_BASEPAG); +#endif + /* Set AC block selections */ + acblk_set((LHGET(cpu.mr_ubr) & UBR_ACBCUR) >> 9, /* Set cur */ + (LHGET(cpu.mr_ubr) & UBR_ACBPREV) >> 6); /* and prev */ + } + +#if 0 /* KLH10_CPU_KS */ + if (!onf) { /* Turning paging/traps off? */ + /* Turn off everything that we use internally, without munging + ** the current settings of the externally visible registers. + */ + cpu.mr_ubraddr = cpu.mr_ebraddr = 0; /* No EPT or UPT */ + acblk_set(0, 0); /* Point to AC block 0 */ + } +#endif /* KS */ + + if (onf) { /* Turning paging/traps on or off? */ + if (cpu.mr_paging) /* If already paging, can stop now */ + return; + +#if KLH10_PAG_ITS + /* The Exec DBRs had better already be set! */ + if (!cpu.pag.pr_dbr3 || !cpu.pag.pr_dbr4) + panic("pag_enable: Exec DBRs not set!"); +#endif + + /* Now set up mapping and context properly */ + + /* Turn on paging! */ + cpu.vmap.user = cpu.pr_umap; /* Make each mode use its own map */ + cpu.vmap.exec = cpu.pr_emap; + vmap_set(cpu.pr_emap, /* Set current & previous VM map */ + (PCFTEST(PCF_UIO) ? cpu.pr_umap : cpu.pr_emap)); + + cpu.mr_paging = TRUE; + + } else { + /* Turn off paging! Do this even if think we're off, for init. */ + cpu.vmap.user = pr_pmap; /* Make both modes use phys map */ + cpu.vmap.exec = pr_pmap; + vmap_set(pr_pmap, pr_pmap); /* Point to phys map */ + + cpu.mr_paging = FALSE; + } +} + +/* PAG_CLEAR - Invalidate all pager maps by clearing all internal entries. +** Perhaps optimize by keeping count of refills? If count zero, +** don't need to re-clear the table. +*/ +static void +pag_clear(void) +{ + PCCACHE_RESET(); /* Invalidate cached PC info */ + pag_mapclr(cpu.pr_umap); + pag_mapclr(cpu.pr_emap); +} + +static void /* Ditto but one map only */ +pag_mapclr(register pment_t *p) +{ + memset((char *)p, 0, PAG_MAXVIRTPGS*sizeof(*p)); +} + +/* Not actually used for anything */ +static void /* Ditto but only half a map */ +pag_segclr(register pment_t *p) +{ + memset((char *)p, 0, (PAG_MAXVIRTPGS/2)*sizeof(*p)); +} + +/* Common IO instructions that manipulate the paging system */ + +/* IO_RDEBR (70124 = CONI PAG,) - Read Executive Base Register +*/ +ioinsdef(io_rdebr) +{ + vm_write(e, cpu.mr_ebr); + return PCINC_1; +} + +#if KLH10_CPU_KL +/* CONSZ PAG, (70130) - KL: Skips if all EBR bits in nonzero E are zero. +** Not explicitly mentioned, but evidently works. +** UUO on KS10. +*/ +ioinsdef(io_sz_pag) +{ + return (va_insect(e) && !(va_insect(e) & RHGET(cpu.mr_ebr))) + ? PCINC_2 : PCINC_1; +} + +/* CONSO PAG, (70134) - KL: Skips if any EBR bits in E are set +** Not explicitly mentioned, but evidently works. +** UUO on KS10. +*/ +ioinsdef(io_so_pag) +{ + return (va_insect(e) & RHGET(cpu.mr_ebr)) + ? PCINC_2 : PCINC_1; +} +#endif /* KL */ + +/* IO_WREBR (70120 = CONO PAG,) - Write Executive Base Register +** Set pointer to EPT, enable paging. +** On ITS, bit EBR_T20 only affects the way MUUOs are trapped. +** Resets (invalidates) cache and page table. +** Note that pag_enable actually effects the EBR alterations. +*/ +ioinsdef(io_wrebr) +{ + register h10_t ebits = va_insect(e); + + if (ebits & EBR_ENABLE) { +#if KLH10_PAG_ITS || KLH10_PAG_KI + if (ebits & EBR_T20) + panic("io_wrebr: Cannot support TOPS-20 (KL) paging"); +#elif KLH10_PAG_KL + /* Only barf on this if KS. KL is now always KL-paging, but + ** apparently ignores state of this bit?! At least diags seem + ** to expect this behavior. + */ +# if KLH10_CPU_KS + if (!(ebits & EBR_T20)) + panic("io_wrebr: Cannot support TOPS-10 (KI) paging"); +# endif +#endif + } else { /* Preserve former state of bit */ + if (RHGET(cpu.mr_ebr) & EBR_T20) + ebits |= EBR_T20; + else ebits &= ~EBR_T20; + } + + /* Always set EBR, but only change paging type bit if enabled */ + RHSET(cpu.mr_ebr, + (ebits & (EBR_CCALK|EBR_CCALD|EBR_T20|EBR_ENABLE|EBR_BASEPAG))); + pag_enable((ebits & EBR_ENABLE) /* Turn paging/traps on/off */ + ? TRUE : FALSE); + pag_clear(); /* Clear page tables */ +#if KLH10_DEBUG + if (cpu.mr_debug) { + putc('[', stderr); + pishow(stderr); pcfshow(stderr, cpu.mr_pcflags); + fprintf(stderr,"%lo: EBR<=%lo]\r\n", + (long) PC_30, (long) cpu.mr_ebraddr); + } +#endif + + return PCINC_1; +} + +/* IO_RDUBR (70104 = DATAI PAG,) - Read User Base Register +*/ +ioinsdef(io_rdubr) +{ +#if KLH10_CPU_KL /* Stuff current PCS into UBR word. */ + op10m_tlz(cpu.mr_ubr, UBR_PCS); + op10m_tlo(cpu.mr_ubr, pag_pcsget()&UBR_PCS); +#endif + vm_write(e, cpu.mr_ubr); + return PCINC_1; +} + +/* IO_WRUBR (70114 = DATAO PAG,) - Write User Base Register +** Resets (invalidates) cache and page table if UPT changes. +** The two bits UBR_SETACB and UBR_SET have already been set in cpu.mr_ubr by +** the initialization code and are never changed thereafter. +*/ +ioinsdef(io_wrubr) +{ + register w10_t w; + w = vm_read(e); /* Get UBR word */ + +#if KLH10_DEBUG + if (cpu.mr_debug) { + putc('[', stderr); + pishow(stderr); pcfshow(stderr, cpu.mr_pcflags); + fprintf(stderr,"%lo: UBR<=", (long) PC_30); + } +#endif + + /* Select AC blocks? */ + if (LHGET(w) & UBR_SETACB) { + + /* It appears that WRUBR always immediately changes + ** the AC blocks even if not paging, for both KL and KS. + */ + acblk_set((LHGET(w) & UBR_ACBCUR) >> 9, /* Set cur */ + (LHGET(w) & UBR_ACBPREV) >> 6); /* and prev */ + PCCACHE_RESET(); /* Invalidate cached PC info */ + + /* Put new selections into UBR */ + LHSET(cpu.mr_ubr, (LHGET(cpu.mr_ubr) & ~(UBR_ACBCUR|UBR_ACBPREV)) + | (LHGET(w) & (UBR_ACBCUR|UBR_ACBPREV))); +#if KLH10_DEBUG + if (cpu.mr_debug) { + fprintf(stderr,"(%o,%o)", (int) (LHGET(w)&UBR_ACBCUR)>>9, + (int) (LHGET(w)&UBR_ACBPREV)>>6); + } +#endif + } + +#if KLH10_CPU_KL + if (LHGET(w) & UBR_SETPCS) { + /* Doesn't matter whether paging on or not, just set pager PCS */ + pag_pcsset(LHGET(w) & UBR_PCS); +#if KLH10_DEBUG + if (cpu.mr_debug) { + fprintf(stderr,"(PCS:%o)", (int)(LHGET(w) & UBR_PCS)); + } +#endif + } +#endif /* KL */ + + /* Select new UPT? */ + if (LHGET(w) & UBR_SET) { + +#if KLH10_CPU_KL + /* Unless explicitly suppressed, setting a new user base address + ** always updates the exec/mem accounts of the previous user. + ** This must be done before mr_ubraddr is clobbered! + */ + if (!(RHGET(w) & UBR_DNUA)) /* Unless bit suppresses it, */ + mtr_update(); /* update previous user accts */ +#endif + + RHSET(cpu.mr_ubr, RHGET(w) & UBR_RHMASK); /* Set UBR RH */ + +#if KLH10_PAG_ITS + LHSET(cpu.mr_ubr, /* ITS - also set UBR LH */ + (LHGET(cpu.mr_ubr) & ~UBR_BASELH) | (LHGET(w) & UBR_BASELH)); + cpu.mr_ubraddr = ((paddr_t)(LHGET(w) & UBR_BASELH) << 18) | RHGET(w); +#else /* DEC */ + cpu.mr_ubraddr = pag_pgtopa(RHGET(w) & UBR_BASEPAG); +#endif + + /* Also must reset cache and page table. + ** KLH10 also resets its PC cache if any. + */ + pag_clear(); /* Or? pag_mapclr(cpu.pr_umap); */ + } +#if KLH10_DEBUG + if (cpu.mr_debug) { + if (LHGET(w) & UBR_SET) + fprintf(stderr,"%lo]\r\n", (long) cpu.mr_ubraddr); + else fprintf(stderr, "--]\r\n"); + } +#endif + return PCINC_1; +} + +/* IO_CLRPT (70110 = BLKO PAG,) - Clear Page Table Entry (reffed by E) +** Per DEC documentation (KSREF.MEM 12/78 p.2-9) this clears the +** page entry in BOTH user and exec maps. +** ITS: Unlike the ITS ucode which only invalidates the first half-page +** (since DEC pages are half the size of ITS pages), this really does +** clear the mapping for the entire ITS page. +** +** NOTE!!!! CLRPT must mask E to make sure the page number is within +** the supported hardware page map! It is entirely possible for E to have +** an absurd value, and this DOES HAPPEN on TOPS-20 due to a monitor bug. +** Specifically, two places in DSKALC.MAC that call MONCLR thinking the arg is +** a page # instead of an address, giving it AC1/ 224000,,2 => page 4,,0 !!! +** This problem is fixed by va_page() which only returns a supported +** virtual page number. +*/ +ioinsdef(io_clrpt) +{ + PCCACHE_RESET(); /* Invalidate cached PC info */ + cpu.pr_umap[va_page(e)] = 0; /* Zapo! */ + cpu.pr_emap[va_page(e)] = 0; + return PCINC_1; +} + +#if KLH10_SYS_ITS + +/* IO_CLRCSH (70100 = BLKI PAG,) - Clear Cache +*/ +ioinsdef(io_clrcsh) +{ +#if 0 /* This isn't actually necessary as the non-existent memory cache + ** has nothing to do with the KLH10 PC cache. FYI only. + */ + PCCACHE_RESET(); /* Invalidate cached PC info */ +#endif + return PCINC_1; +} +#endif /* ITS */ + +/* Auxiliaries */ + +/* PFBEG, PFEND - Start and end debug info for page failure trap */ +static void +pfbeg(void) +{ + fprintf(stderr, "[PFAIL(%s,%lo,%s,\"%s\") ", + (cpu.pag.pr_fmap == cpu.pr_umap ? "U" + : (cpu.pag.pr_fmap == cpu.pr_emap ? "E" + : (cpu.pag.pr_fmap == pr_pmap ? "P" + : (cpu.pag.pr_fmap == NULL ? "IO" + : "\?\?\?")))), (long) cpu.pag.pr_fref, + ((cpu.pag.pr_facf&VMF_WRITE) ? "W" + : ((cpu.pag.pr_facf&VMF_READ) ? "R" + : "0")), + cpu.pag.pr_fstr ? cpu.pag.pr_fstr : "?" ); + pishow(stderr); pcfshow(stderr, cpu.mr_pcflags); + fprintf(stderr,"%lo: => ", (long) PC_30); +} + +static void +pfend(void) +{ + pishow(stderr); pcfshow(stderr, cpu.mr_pcflags); + fprintf(stderr,"%lo:]\r\n", (long) PC_30); +} + +static uint32 +fetch32(register vaddr_t e) +{ + register w10_t w; + w = vm_read(e); /* Get word from c(E) */ + return ((uint32)LHGET(w) << 18) | RHGET(w); /* Convert to 32-bit value */ +} + +static void +store32(register vaddr_t e, + register uint32 val) +{ + register w10_t w; + LRHSET(w, (val>>18)&H10MASK, val & H10MASK); /* Put val into word */ + vm_write(e, w); /* Store at c(E) */ +} + +#if KLH10_PAG_ITS || KLH10_PAG_KI + +/* PAG_REFILL - Called when a virtual mem ref failed due to invalid +** access bits in the page map. Attempts a refill of the entry. +** On success, returns a vmptr_t to the physical memory location. +** On failure, depending on whether VMF_NOTRAP is set in the access flags, +** Not set - calls pag_fail() to carry out a page fault trap. +** Set - returns NULL. +*/ +vmptr_t +pag_refill(register pment_t *p, /* Page map pointer */ + vaddr_t e, /* Virtual address to map */ + pment_t f) /* Access flags */ +{ + register pment_t ent; /* Pager table entry, used by "hardware" */ + register pagno_t pag; + register h10_t mapent; /* Map entry set up by software */ + register vaddr_t addr; /* Address of map entry word */ + + pag = va_page(e); /* Get virtual page number */ + + /* Find address of correct page map entry from software table */ + +#if KLH10_PAG_ITS + /* Find which DBR to refill from; depends on user/exec context and + ** whether in low or high memory. + */ + if (p == cpu.pr_umap) { /* Assume user map (most common) */ + addr = ((pag & 0200) ? cpu.pag.pr_dbr2 : cpu.pag.pr_dbr1) + + ((pag & 0177) >> 1); + } else if (p == cpu.pr_emap) { /* Else must be exec map */ + addr = ((pag & 0200) ? cpu.pag.pr_dbr3 : cpu.pag.pr_dbr4) + + ((pag & 0177) >> 1); + } +#elif KLH10_PAG_KI + if (p == cpu.pr_umap) /* Assume user map (most common) */ + addr = cpu.mr_ubraddr + UPT_PMU + (pag>>1); + else if (p == cpu.pr_emap) { /* Else must be exec map */ + if (pag & 0400) + addr = cpu.mr_ebraddr + EPT_PME400 + ((pag&0377)>>1); + else { + if (pag >= 0340) /* Get special seg from UPT? */ + addr = cpu.mr_ubraddr + UPT_PME340 + ((pag-0340)>>1); + else + addr = cpu.mr_ebraddr + EPT_PME0 + (pag>>1); + } + } +#endif + else if (p == pr_pmap) { + /* Physical map -- turn reference into a NXM. */ + pag_nxmfail((paddr_t) va_30(e), f, "paging-off refill"); + return vm_physmap(0); /* In case return, provide a ptr */ + + } else { + panic("pag_refill: unknown map! p=0x%lX, e=%#lo, f=%#lo", + (long)p, (long)e, (long)f); + } + + + /* Fetch map entry from PDP-10 memory. This is a halfword table + ** hence we test low bit of page # to see if entry is in LH or RH. + */ + mapent = (pag & 01) /* Get RH or LH depending on low bit */ + ? RHPGET(vm_physmap(addr)) : LHPGET(vm_physmap(addr)); + + /* Transform software pagemap flags into internal "hardware" flags. */ +#if KLH10_PAG_ITS + switch (mapent & PM_ACC) { /* Check out ITS access bits */ + case PM_ACCNON: /* 00 - No access */ + ent = 0; + break; + case PM_ACCRO: /* 01 - Read Only */ + case PM_ACCRWF: /* 10 - Convert R/W/F to RO */ + ent = (f & VMF_WRITE) /* Fail if writing, else win */ + ? 0 : VMF_READ; + break; + case PM_ACCRW: /* 11 - Read/Write */ + ent = VMF_READ | VMF_WRITE; /* Always win */ + break; + } +#elif KLH10_PAG_KI + switch (mapent & (PM_ACC|PM_WRT)) { + case 0: + case PM_WRT: /* No access */ + ent = 0; + break; + case PM_ACC: /* Read-Only */ + ent = (f & VMF_WRITE) /* Fail if writing, else win */ + ? 0 : VMF_READ; + break; + case PM_ACC|PM_WRT: /* Read/Write */ + ent = VMF_READ | VMF_WRITE; /* Always win */ + break; + } +#endif + + /* Now check access... */ + if (ent) { +#if KLH10_PAG_KI + /* Should check here for getting a physical page # greater than + ** our available physical memory. But as long as we always support + ** the max possible (512K for KS, etc) we're safe. + */ + if ((mapent & PM_PAG) > PAG_PAMSK) { + /* Ugh, fail with NXM */ + cpu.pag.pr_fref = pag_pgtopa(mapent & PM_PAG) | va_pagoff(e); + cpu.pag.pr_fstr = "NXM page"; + cpu.pag.pr_flh = + (p == cpu.pr_umap ? PF_USER : 0) /* U */ + | PMF_NXMERR /* fail type 37 */ + | PF_VIRT; /* V=1 */ + + cpu.aprf.aprf_set |= APRF_NXM; /* Report as APR flag too */ + apr_picheck(); /* Maybe set PI (will happen after pagefail) */ + + } else +#endif + { + p[pag] = ent | (mapent & PM_PAG); /* Won, set hardware table */ + return vm_physmap(pag_pgtopa(mapent & PM_PAG) | va_pagoff(e)); + } + } else { + + /* Failed, store info so that pag_fail can trap later if caller + ** decides to invoke it. + */ + cpu.pag.pr_fref = va_insect(e); /* Save virtual addr */ + cpu.pag.pr_fstr = (ent & VMF_READ) ? "W in RO page" : "NoAcc page"; +#if KLH10_PAG_ITS + cpu.pag.pr_flh = (p == cpu.pr_umap ? PF_USR : 0) + | ((f & VMF_WRITE) ? PF_WRT : 0) + | ((mapent & PM_29) ? PF_29 : 0) + | ((mapent & PM_28) ? PF_28 : 0); +#elif KLH10_PAG_KI +# if KLH10_CPU_KS + cpu.pag.pr_flh = PF_VIRT + | (p == cpu.pr_umap ? PF_USER : 0) + | ((f & VMF_WRITE) ? PF_WREF : 0) + | ((mapent & PM_ACC) ? (PF_ACC + | ((mapent & PM_SFT) ? PF_SFT : 0)) + : 0); +# elif KLH10_CPU_KL + cpu.pag.pr_flh = + (p == cpu.pr_umap ? PF_USER : 0) /* U */ + | ((mapent & PM_ACC) ? PF_ACC : 0) /* A */ + | ((mapent & PM_WRT) ? PF_WRT : 0) /* W */ + | ((mapent & PM_SFT) ? PF_SFT : 0) /* S */ + | ((f & VMF_WRITE) ? PF_WREF : 0) /* T */ + | ((mapent & PM_PUB) ? PF_PUB : 0) /* P */ + | ((mapent & PM_CSH) ? PF_CSH : 0) /* C */ + | PF_VIRT; /* V */ +# endif +#endif + } + + cpu.pag.pr_fmap = p; + cpu.pag.pr_facf = f; + + if (!(f & VMF_NOTRAP)) /* If caller isn't suppressing traps, */ + pag_fail(); /* take page failure trap now! */ + return NULL; /* Otherwise just return NULL */ +} +#endif /* KLH10_PAG_ITS || KLH10_PAG_KI */ + + +#if KLH10_CPU_KS + +/* PAG_IOFAIL - Called from IO code for bad unibus address +*/ +void +pag_iofail(paddr_t ioaddr, + int bflg) /* True if byte-mode reference */ +{ +#if KLH10_PAG_ITS + cpu.pag.pr_flh = (PF_NXI|PF_PHY|PF_IO + | (bflg ? PF_BYT : 0) + | (cpu.vmap.cur == cpu.pr_umap ? PF_USR : 0) + ); +#elif KLH10_PAG_KI || KLH10_PAG_KL + cpu.pag.pr_flh = (PF_HARD|PF_VIRT|PF_IOREF + | (bflg ? PF_IOBYTE : 0) + | (cpu.vmap.cur == cpu.pr_umap ? PF_USER : 0) + ); +#endif + cpu.pag.pr_fref = ioaddr; + cpu.pag.pr_fstr = "NX IO addr"; + cpu.pag.pr_fmap = NULL; + cpu.pag.pr_facf = 0; + pag_fail(); +} +#endif /* KLH10_CPU_KS */ + + +/* PAG_FAIL - Instruction aborted due to page failure (mem ref). +** Save context, set up new, then longjmp to main loop. +** NOTE: the virtual address given must be correct! This routine assumes +** the LH (section or controller) bits are meaningful when building +** the page fail word. +** +** Note that ITS KS10 pager microcode differs from DEC: +** - Words are in EPT, not UPT. +** - Page fail vector saves PC+flags in 1 word (like T10), not 2 like T20. +** - Instead of one vector, there are 8, one for each PI level plus +** the non-PI case! +** +;;; In the ITS microcode the three words used to deliver a page fail are +;;; determined from the current interrupt level. At level I, the page fail +;;; word is stored in EPTPFW+<3*I>, the old PC is stored in EPTPFO+<3*I>, +;;; and the new PC is obtained from EPTPFN+<3*I>. If no interrupts are in +;;; progress we just use EPTPFW, EPTPFO and EPTPFN. + +** DEC T10 and T20 differ mainly in that the old flags+PC are 1 word on T10, +** 2 words on T20. +** +** The Extended KL case is basically identical to KS T20. +** +** Extended KL behavior: +** PCS is saved in the flag word ONLY if old mode was exec. +** New flags are all cleared; PCP and PCU are not set. +** Evidently no new PCS is set. +** +** Also, on the KL, for AR and ARX parity errors the "word with correct +** parity" is saved in AC0, block 7. But we never get such errors +** except for physical NXMs. +** Also, on the KL, for IO Page Failures (ie PF during PI interrupt instr) +** the PI function word is stored in AC2, block 7. +** T20 monitor code says "as of KL ucode v257", 24-Feb-82. +*/ +void +pag_fail(void) +{ + register w10_t w; + register paddr_t paddr; + + /* Sanity checks */ + if (!cpu.mr_paging && cpu.mr_exsafe) { + pfbeg(); /* Do initial barf */ + fprintf(stderr, "Paging OFF, but trapping!]\r\n"); + if (cpu.mr_exsafe >= 2) + apr_halt(HALT_EXSAFE); + } + if (cpu.mr_injrstf) /* Internal error */ + panic("pag_fail: page fail in JRSTF!"); + + /* First undo whatever we were in the middle of. */ + +#if KLH10_ITS_1PROC + if (cpu.mr_in1proc) a1pr_undo(); /* Undo if in one-proceed */ +#elif KLH10_CPU_KI || KLH10_CPU_KL + if (cpu.mr_inafi) afi_undo(); /* Undo if in AFI */ +#endif + if (cpu.mr_intrap) trap_undo(); /* Undo if in trap instruction */ + if (cpu.mr_inpxct) pxct_undo(); /* Undo if PXCT operand */ + /* (This may restore correct PC!) */ + if (cpu.mr_inpi) { + /* Page failure during interrupt instruction! + ** KS always drops dead. + ** KL does something a little more elaborate. Since it really + ** ought not to happen, and can be hard to figure out after the fact, + ** some error output is given even though execution continues + ** to emulate the KL. + ** Note that the PI function word has already been stored in AC3 BLK7. + ** The page fail word needs to be put into AC2 BLK7. + */ +#if KLH10_CPU_KL + if (cpu.mr_exsafe || cpu.mr_debug) { + pfbeg(); /* Barf to show what's going on */ + fprintf(stderr, "PI IO Page Fail]\r\n"); + if (cpu.mr_exsafe >= 2) + apr_halt(HALT_EXSAFE); + } + cpu.aprf.aprf_set |= APRF_IOPF; /* Set IO Page Fail bit */ + apr_picheck(); /* Trigger new PI, we hope */ + LRHSET(w, cpu.pag.pr_flh | (cpu.pag.pr_fref >> H10BITS), + cpu.pag.pr_fref & H10MASK); + cpu.acblks[7][2] = w; /* Store page fail word here */ + cpu.mr_inpi = FALSE; /* Say no longer in PI */ + apr_int(); /* Back to main loop */ +#else /* KA+KI+KS */ + pfbeg(); + cpu.mr_inpi = FALSE; /* Say no longer in PI */ + panic("Illegal Instruction halt - page fail during int instr!"); +#endif /* !KL */ + } + +#if KLH10_DEBUG + if (cpu.mr_debug) + pfbeg(); /* Output page fail debugging info */ +#endif + + /* Now put together a page fail word. + ** Note that the address (pr_fref) is NOT masked here; that's left + ** up to the refill/map code, which knows how many bits to use. + */ + LRHSET(w, cpu.pag.pr_flh | (cpu.pag.pr_fref >> H10BITS), + cpu.pag.pr_fref & H10MASK); + + /* Now have page fail word. Deliver the fault... */ +#if KLH10_PAG_ITS +# define XPT_PFW EPT_PFW /* ITS uses words in EPT! */ +# define XPT_PFO EPT_PFO +# define XPT_PFN EPT_PFN + /* Find highest PIP and get number for that */ + paddr = cpu.mr_ebraddr + (3 * pilev_nums[cpu.pi.pilev_pip]); + +#elif KLH10_PAG_KI || KLH10_PAG_KL +# define XPT_PFW UPT_PFW /* T10/T20 use words in UPT! */ +# define XPT_PFO UPT_PFO +# define XPT_PFN UPT_PFN + paddr = cpu.mr_ubraddr; + +#endif + + vm_pset(vm_physmap(XPT_PFW+paddr), w); /* Store page fail word */ + + /* Now save old context (PC, flags, PCS). + ** This is *very* processor-specific and the code here + ** doesn't account for all processor/system variations yet. + */ +#if (KLH10_CPU_KLX || KLH10_CPU_KS) && KLH10_PAG_KL /* T20 on KS or KLX */ + /* Use 4-word page-fail block with room for big PCs */ +# if KLH10_EXTADR + if (!PCFTEST(PCF_USR)) /* Make saved flag word */ + LRHSET(w, cpu.mr_pcflags, pag_pcsget()); /* save PCS */ + else /* Unless in user mode */ +# endif + LRHSET(w, cpu.mr_pcflags, 0); + vm_pset(vm_physmap(UPT_PFF+paddr), w); /* Store flag word */ + +# if KLH10_EXTADR + /* NOTE: Empirical testing on a real 2065 indicates that only 23 + ** bits of PC appear to be saved, not the full 30 bits. + ** Hence the mask by VAF_VSMSK for now (later PC or PC_SECT may be + ** pre-cleaned, rendering explicit mask unnecessary). + */ + LRHSET(w, PC_SECT & VAF_VSMSK, PC_INSECT); /* Make old PC word */ +# else + LRHSET(w, 0, PC_INSECT); /* Make old PC word */ +# endif + vm_pset(vm_physmap(XPT_PFO+paddr), w); /* Store old PC */ + + w = vm_pget(vm_physmap(XPT_PFN+paddr)); /* Get new PC word */ +# if KLH10_EXTADR + PC_SET30((((paddr_t)LHGET(w)<<18) | RHGET(w)) & MASK30); +# else + PC_SET30(RHGET(w)); /* Set new PC */ +# endif + cpu.mr_pcflags = 0; /* and clear flags */ + +#else /* KL0/T20, KLX/T10, KS/T10, ITS */ + + /* Use 3-word page-fail block with old PC+flags */ + LRHSET(w, cpu.mr_pcflags, PC_INSECT); /* Make old PC word */ + vm_pset(vm_physmap(XPT_PFO+paddr), w); /* Store old PC */ + w = vm_pget(vm_physmap(XPT_PFN+paddr)); /* Get new PC word */ + PC_SET30(RHGET(w)); /* Set new PC */ + cpu.mr_pcflags = LHGET(w) & PCF_MASK; /* and new flags */ +#endif + + apr_pcfcheck(); /* Check flags for any changes */ + +#if KLH10_DEBUG + if (cpu.mr_debug) + pfend(); /* Finish up debug info */ +#endif + + apr_int(); /* Back to main loop */ +} + +#if KLH10_PAG_ITS + +/* ITS Page register instructions */ + +/* IO_LDBR1 - (BLKI .WR.,) +** IO_LDBR2 - (DATAI .WR.,) +** IO_LDBR3 - (BLKO .WR.,) +** IO_LDBR4 - (DATAO .WR.,) +*/ +#define setdbr(dbr) dbr = va_insect(e); pag_clear(); return PCINC_1 +ioinsdef(io_ldbr1) { setdbr(cpu.pag.pr_dbr1); } +ioinsdef(io_ldbr2) { setdbr(cpu.pag.pr_dbr2); } +ioinsdef(io_ldbr3) { setdbr(cpu.pag.pr_dbr3); } +ioinsdef(io_ldbr4) { setdbr(cpu.pag.pr_dbr4); } +#undef setdbr + +/* IO_LPMR - (CONSO .WR.,) +** Load DBR block from memory into pager registers +*/ +ioinsdef(io_lpmr) +{ + register w10_t w; + register int i; + vmptr_t p[5]; + + /* LPMR isn't that frequent, so resolve the 5 refs with a loop */ + for (i = 0; i < 5; ++i) { + if (i) va_inc(e); /* Bump addr, may wrap */ + p[i] = vm_xrwmap(e, VMF_READ); /* Ensure all locations are valid */ + } + + w = vm_pget(p[0]); /* Get DBR1 */ + cpu.pag.pr_dbr1 = ((uint32)LHGET(w) << 18) | RHGET(w); + w = vm_pget(p[1]); /* Get DBR2 */ + cpu.pag.pr_dbr2 = ((uint32)LHGET(w) << 18) | RHGET(w); + w = vm_pget(p[2]); /* New quantum timer */ +#if KLH10_CPU_KS + /* KS quantum field is 044000 (high 32 bits; low 4 ignored) */ + cpu.pag.pr_quant = ((uint32)LHGET(w) << (18-4)) | (RHGET(w)>>4); +#else +# error "io_lpmr() not implemented for non-KS10" +#endif + if (!cpu.pi.pilev_pip) /* Start it going if OK */ + cpu.pag.pr_quant = quant_unfreeze(cpu.pag.pr_quant); + w = vm_pget(p[3]); + cpu.pag.pr_ujpc = ((uint32)LHGET(w) << 18) | RHGET(w); + w = vm_pget(p[4]); + cpu.pag.pr_ejpc = ((uint32)LHGET(w) << 18) | RHGET(w); +#if KLH10_ITS_JPC /* Update JPC for mode */ + cpu.mr_jpc = (cpu.mr_usrmode ? cpu.pag.pr_ujpc : cpu.pag.pr_ejpc); +#endif + + /* Now must reset cache and page tables!! */ + pag_clear(); + + return PCINC_1; +} + +/* IO_SDBR1 - (BLKI .RD.,) +** IO_SDBR2 - (DATAI .RD.,) +** IO_SDBR3 - (BLKO .RD.,) +** IO_SDBR4 - (DATAO .RD.,) +*/ +static pcinc_t getdbr(vaddr_t, uint32); + +ioinsdef(io_sdbr1){ return getdbr(e, cpu.pag.pr_dbr1); } +ioinsdef(io_sdbr2){ return getdbr(e, cpu.pag.pr_dbr2); } +ioinsdef(io_sdbr3){ return getdbr(e, cpu.pag.pr_dbr3); } +ioinsdef(io_sdbr4){ return getdbr(e, cpu.pag.pr_dbr4); } + +static pcinc_t +getdbr(vaddr_t e, uint32 dbr) +{ + register w10_t w; + LRHSET(w, (dbr>>18)&H10MASK, dbr & H10MASK); + vm_write(e, w); + return PCINC_1; +} + +/* IO_SPM - (CONSO .RD.,) +** Store DBRs etc in memory +*/ +ioinsdef(io_spm) +{ + register w10_t w; + register int32 qcnt; + vmptr_t p[5]; + + /* SPM isn't that frequent, so resolve the 5 refs with a loop */ + for (qcnt = 0; qcnt < 5; ++qcnt) { + if (qcnt) va_inc(e); /* Bump addr, may wrap */ + p[qcnt] = vm_wrtmap(e); /* Ensure all locations are valid */ + } + + LRHSET(w, (cpu.pag.pr_dbr1>>18)&H10MASK, cpu.pag.pr_dbr1 & H10MASK); + vm_pset(p[0], w); /* Store DBR1 */ + LRHSET(w, (cpu.pag.pr_dbr2>>18)&H10MASK, cpu.pag.pr_dbr2 & H10MASK); + vm_pset(p[1], w); /* Store DBR2 */ + qcnt = (cpu.pi.pilev_pip ? cpu.pag.pr_quant + : quant_freeze(cpu.pag.pr_quant)); +#if KLH10_CPU_KS + /* KS quantum field is 044000 (high 32 bits; low 4 ignored) */ + LRHSET(w, (qcnt>>(18-4))&H10MASK, ((qcnt<<4) & H10MASK)); +#else +# error "io_spm() not implemented for non-KS10" +#endif + vm_pset(p[2], w); /* Store Quantum timer */ +#if KLH10_ITS_JPC + if (cpu.mr_usrmode) /* Update latest JPC for mode */ + cpu.pag.pr_ujpc = cpu.mr_jpc; + else cpu.pag.pr_ejpc = cpu.mr_jpc; +#endif + LRHSET(w, (cpu.pag.pr_ujpc>>18)&H10MASK, cpu.pag.pr_ujpc & H10MASK); + vm_pset(p[3], w); + LRHSET(w, (cpu.pag.pr_ejpc>>18)&H10MASK, cpu.pag.pr_ejpc & H10MASK); + vm_pset(p[4], w); + return PCINC_1; +} +#endif /* KLH10_PAG_ITS */ + +#if KLH10_PAG_KL /* DEC instruction for KL (T20) paging */ + +/* MAP - Map an Address. +** This is not an IO-class instruction and the PRM says nothing +** about its legality in user mode, but it can't hurt, so no +** user-mode checking is done. +** HOWEVER, PRM 3-42 says on KL same IO-class restrictions do apply. +** NOTE: this code always does a full refill in order to find all of +** the entry bits; it does not look in the "hardware" map entry +** because we only keep a couple of the desired bits there to save +** space and improve cacheability. +** Since MAP is not called frequently this is OK speedwise, +** but this may consitute a difference from the real hardware +** (suppose something changed the map pointers without invalidating +** the page table). +** Also note: +** MAP can be invoked by a PXCT, so needs to use XRW mapping when +** checking out the refill. +** Question: +** What happens if NXM interrupts are enabled for APR and a NXM +** happens as a result of MAP? Is it ignored, or is the interrupt +** still triggered although the page fail trap isn't? +** For the time being, I'll assume YES. This is probably a non-issue +** anyway as enabling those ints is discouraged. +*/ +insdef(i_map) +{ + register vmptr_t vmp; + register w10_t w; + register h10_t pfent; + register h10_t pflags; + register paddr_t pa; + +#if KLH10_CPU_KL + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) + return i_muuo(op, ac, e); +#endif + + if (vmp = pag_refill(cpu.vmap.xrw, e, VMF_NOTRAP)) { + pa = vmp - cpu.physmem; /* Recover physical addr */ + pfent = cpu.pag.pr_flh; /* And fetch access bits for page */ + + /* Now transform access bits into MAP result bits. */ +#if KLH10_MCA25 /* PF_KEEP is a re-use of PF_VIRT */ + pflags = PF_ACC; + if (pfent & PT_KEEP) pflags |= PF_KEEP; +#else + pflags = PF_ACC | PF_VIRT; +#endif +#if KLH10_CPU_KL + if (pfent & PT_PACC) pflags |= PF_PUB; +#endif + if (pfent & PT_WACC) pflags |= PF_WRT; + if (pfent & PT_CACHE) pflags |= PF_CSH; + if (pfent & CST_MBIT) pflags |= PF_MOD; /* Special non-PT bit */ + if (cpu.vmap.xrw == cpu.pr_umap) pflags |= PF_USER; + + } else { + pflags = cpu.pag.pr_flh; /* Just get fail bits already set up */ + pa = cpu.pag.pr_fref & MASK22; /* And recover failing addr */ + } + LRHSET(w, (pflags | (pa >> H10BITS)), pa & H10MASK); + ac_set(ac, w); /* Store result in given AC */ + return PCINC_1; +} +#endif /* KLH10_PAG_KL */ + +#if KLH10_PAG_KI + +/* MAP instruction for KI (T10) paging. +** KI paging MAP is different enough from KL/T20 that it makes +** more sense to keep it a separate routine. In particular, there +** is no way for a map entry to "fail", even though a pag_refill call +** could cause a failure. +*/ + +insdef(i_map) +{ + register h10_t pflags; + register paddr_t pa; + register pment_t *p; + register pagno_t pag; + register h10_t mapent; /* Map entry set up by software */ + register vaddr_t addr; /* Address of map entry word */ + register w10_t w; + +#if KLH10_CPU_KL + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) + return i_muuo(op, ac, e); +#endif + + /* Find map entry. This does a refill lookup instead of checking + ** the hardwage page table. + */ + pag = va_page(e); /* Get virtual page number */ + p = cpu.vmap.xrw; /* Use XRW map (may be PXCT'd) */ + if (p == cpu.pr_umap) /* Assume user map (most common) */ + addr = cpu.mr_ubraddr + UPT_PMU + (pag>>1); + else if (p == cpu.pr_emap) { /* Else must be exec map */ + if (pag & 0400) + addr = cpu.mr_ebraddr + EPT_PME400 + ((pag&0377)>>1); + else { + if (pag >= 0340) /* Get special seg from UPT? */ + addr = cpu.mr_ubraddr + UPT_PME340 + ((pag-0340)>>1); + else + addr = cpu.mr_ebraddr + EPT_PME0 + (pag>>1); + } + } else { + if (p != pr_pmap) /* Permit phys map to return 0 */ + panic("[i_map: unknown map %lo!]", (long)p); + op10m_setz(w); /* Return 0 (what else?) */ + ac_set(ac, w); + return PCINC_1; + } + + /* Fetch map entry from PDP-10 memory. This is a halfword table + ** hence we test low bit of page # to see if entry is in LH or RH. + */ + mapent = (pag & 01) /* Get RH or LH depending on low bit */ + ? RHPGET(vm_physmap(addr)) : LHPGET(vm_physmap(addr)); + + if (mapent & PM_ACC) { /* Valid mapping? */ + /* Now transform access bits into MAP result bits. */ + pflags = + (p == cpu.pr_umap ? PF_USER : 0) /* U */ + | ((mapent & PM_ACC) ? PF_ACC : 0) /* A */ + | ((mapent & PM_WRT) ? PF_WRT : 0) /* W */ + | ((mapent & PM_SFT) ? PF_SFT : 0) /* S */ + /* T=0 */ + | ((mapent & PM_PUB) ? PF_PUB : 0) /* P */ + | ((mapent & PM_CSH) ? PF_CSH : 0) /* C */ + | PF_VIRT; /* V=1 */ + + /* Always return phys addr, even if bogus and would NXM if tried */ + pa = pag_pgtopa(mapent & PM_PAG) | va_pagoff(e); + LRHSET(w, (pflags | (pa >> H10BITS)), pa & H10MASK); + } else + LRHSET(w, PF_VIRT, 0); /* Invalid mapping */ + + ac_set(ac, w); /* Store result in given AC */ + return PCINC_1; +} +#endif /* KLH10_PAG_KI */ + +/* DEC (mainly TOPS-20) Page register instructions */ + +#if KLH10_PAG_KL && KLH10_CPU_KS + +/* WRSPB (70240 = BLKI MTR,) - Write SPT Base Address +** Sets SPB from c(E). +** IO-class instruction, assumed legal only in Exec or if User-IOT set. +** Note: one might expect this to invalidate the cache and page table, +** but the PRM makes no mention of this action. So we don't. +*/ +ioinsdef(io_wrspb) +{ + cpu.pag.pr_spb = fetch32(e); + return PCINC_1; +} + +/* RDSPB (70200 = BLKI TIM,) - Read SPT Base Address +** Reads SPB into c(E). +** IO-class instruction, assumed legal only in Exec or if User-IOT set. +*/ +ioinsdef(io_rdspb) +{ + store32(e, cpu.pag.pr_spb); + return PCINC_1; +} + +/* WRCSB (70244 = DATAI MTR,) - Write CST Base Address +** Sets CSB from c(E). +** IO-class instruction, assumed legal only in Exec or if User-IOT set. +** Note: one might expect this to invalidate the cache and page table, +** but the PRM makes no mention of this action. So we don't. +*/ +ioinsdef(io_wrcsb) +{ + cpu.pag.pr_csb = fetch32(e); + return PCINC_1; +} + +/* RDCSB (70204 = DATAI TIM,) - Read CST Base Address +** Reads CSB into c(E). +** IO-class instruction, assumed legal only in Exec or if User-IOT set. +*/ +ioinsdef(io_rdcsb) +{ + store32(e, cpu.pag.pr_csb); + return PCINC_1; +} + +/* WRCSTM (70254 = DATAO MTR,) - Write CST Mask Register +** Sets CSTM from c(E). +** IO-class instruction, assumed legal only in Exec or if User-IOT set. +*/ +ioinsdef(io_wrcstm) +{ + cpu.pag.pr_cstm = vm_read(e); + return PCINC_1; +} + +/* RDCSTM (70214 = DATAO TIM,) - Read CST Mask Register +** Reads CSTM into c(E). +** IO-class instruction, assumed legal only in Exec or if User-IOT set. +*/ +ioinsdef(io_rdcstm) +{ + vm_write(e, cpu.pag.pr_cstm); + return PCINC_1; +} + +/* WRPUR (70250 = BLKO MTR,) - Write Process Use Register +** Sets PUR from c(E). +** IO-class instruction, assumed legal only in Exec or if User-IOT set. +*/ +ioinsdef(io_wrpur) +{ + cpu.pag.pr_pur = vm_read(e); + return PCINC_1; +} + +/* RDPUR (70210 = BLKO TIM,) - Read Process Use Register +** Reads PUR into c(E). +** IO-class instruction, assumed legal only in Exec or if User-IOT set. +*/ +ioinsdef(io_rdpur) +{ + vm_write(e, cpu.pag.pr_pur); + return PCINC_1; +} + +#endif /* KL-paging && KS */ + +#if KLH10_CPU_KL + +/* DEC KL10 Cache Instructions +** +** These are here because they have more to do with paging +** than anything else. For the KLH10, they basically do +** nothing at all, since there is no cache to manage. +*/ + +/* +** - (70140 = BLKI CCA,) - Sweep Cache, uselessly +** SWPIA (70144 = DATAI CCA,) - Sweep Cache, Invalidate All Pages +** SWPVA (70150 = BLKO CCA,) - Sweep Cache, Validate All Pages +** SWPUA (70154 = DATAO CCA,) - Sweep Cache, Unload All Pages +** - (70160 = CONO CCA,) - Sweep Cache, uselessly +** SWPIO (70164 = CONI CCA,) - Sweep Cache, Invalidate One Page +** SWPVO (70170 = CONSZ CCA,) - Sweep Cache, Validate One Page +** SWPUO (70174 = CONSO CCA,) - Sweep Cache, Unload One Page +*/ +ioinsdef(io_swp) /* Invoked directly by BLKI and CONO */ +{ + /* Pretend sweep is done instantly, and hope nothing breaks. */ +#if 0 /* Always off, to avoid needing more than 16 bits */ + cpu.aprf.aprf_set &= ~APRR_SWPBSY; +#endif + cpu.aprf.aprf_set |= APRF_SWPDON; /* Say sweep done */ + apr_picheck(); /* Trigger int if desired */ + return PCINC_1; +} + +ioinsdef(io_swpia) { return io_swp(e); } +ioinsdef(io_swpva) { return io_swp(e); } +ioinsdef(io_swpua) { return io_swp(e); } +ioinsdef(io_swpio) { return io_swp(e); } +ioinsdef(io_swpvo) { return io_swp(e); } +ioinsdef(io_swpuo) { return io_swp(e); } + + +/* WRFIL (70010 = BLKO APR,) - Write Refill Table +** KL10 hack for programming the cache. Fortunately no need to +** do anything here! +*/ +ioinsdef(io_wrfil) +{ + return PCINC_1; +} + +#endif /* KL */ + +#if KLH10_CPU_KL + +/* KL10 Address Break and Memory Maintenance instructions */ + +/* (70014 = DATAO APR,) - KL: Set Address Break register +** Sets address break from c(E). +** Note: This is not actually implemented, for obvious efficiency +** reasons. It certainly *could* be if that proved desirable. +*/ +ioinsdef(io_do_apr) +{ + cpu.mr_adrbrk = vm_read(e); + return PCINC_1; +} + +/* (70004 = DATAI APR,) - KL: Read Address Break register +** Reads address break into c(E). +*/ +ioinsdef(io_di_apr) +{ + vm_write(e, cpu.mr_adrbrk); + return PCINC_1; +} + + +/* RDERA (70040 = BLKI PI,) - KL: Read Error Address Register +** Reads ERA into c(E). +** +** To fake out the APR locking flag hackery, simply zap the ERA anytime +** this is called and no locking flag is set. +*/ +ioinsdef(io_rdera) +{ + w10_t w; + + if (!(cpu.aprf.aprf_set & (APRF_NXM|APRF_MBPAR|APRF_ADPAR))) + cpu.pag.pr_era = 0; /* No locking flags, zap ERA */ + + LRHSET(w, (cpu.pag.pr_era >> 18) & 07777, cpu.pag.pr_era & H10MASK); + vm_write(e, w); + return PCINC_1; +} + + +/* SBDIAG (70050 = BLKO PI,) - KL: S Bus Diagnostic Function +** Send c(E) to S-bus memory controller, read return word into E+1. +** Controller # in bits 0-4, function code in 31-35. +** +** Apparently function 0 returns -1 for a "non-ex Fbus" controller, 0 for a +** non-existent controller? +** +** Documentation for the function and return words can be found in +** the PRM, Appendix G, "Handling Memory". +** +** The support here is the minimum necessary to fake out TOPS-20. +** For now it pretends to have MF20 memories with the maximum +** amount of physical memory (22 bits). Actually it takes advantage of +** the trivial way T20 does its memory init by simply pretending to have a +** single MF20 (controller 010) that responds "here!" to the entire +** physical memory range! +*/ + +/* SBDIAG function word fields */ +#define SBD_FCTLR 0760000 /* LH: Controller # (MF20s are 10-17) */ +#define SBD_FFUN 037 /* RH: Function # */ +# define SBD_FN_0 0 +# define SBD_FN_1 01 +/* Functions 2, 6, and 7 are only used to attempt error correction. */ +/* 7 is used by KL diags (SUBKL) to initialize memory. */ +# define SBD_FN_7 07 +# define SBD_FN_12 012 + +#define SBD_F0CLR 010000 /* LH: F0 Clear error flags */ +#define SBD_R0ERRS 0770000 /* LH: Error bits */ + +#define SBD_R1TYP 01700 /* LH: Controller Type Field */ +# define SBD_R1TMA20 00100 /* LH: MA20 */ +# define SBD_R1TDMA20 00200 /* LH: DMA20 */ +# define SBD_R1TMB20 00300 /* LH: MB20 */ +# define SBD_R1TMF20 00500 /* LH: MF20 */ +#define SBD_R1OFFL 02000 /* RH: Off Line */ + +#define SBD_F12BLKNO 0176000 /* RH: F12 Block # - high 6 bits of */ + /* 22-bit phys addr (block=64K) */ +#define SBD_R12BLKNH 010 /* LH: Block Disable ("Not here") */ + + +ioinsdef(io_sbdiag) +{ + register w10_t w; + register paddr_t pa; + + w = vm_read(e); /* Read the function word */ + if ((LHGET(w) >> 13) == 010) { /* Controller # we support? */ + + switch (RHGET(w) & SBD_FFUN) { /* Get function # */ + case SBD_FN_0: + LRHSET(w, 06000, 0); /* Say nothing wrong */ + break; + + case SBD_FN_1: + LRHSET(w, SBD_R1TMF20, 0); /* Say MF20 and online */ + break; + + case SBD_FN_7: + if (!op10m_tlnn(w, 04)) /* If 7-14 enabled, return them */ + LHSET(w, 0); /* else clear LH */ + RHSET(w, 0); /* always clear RH */ + break; + + case SBD_FN_12: + pa = (RHGET(w) & SBD_F12BLKNO) >> 10; /* Get block # */ + pa <<= (16 - PAG_BITS); /* Find phys page # it represents */ + if (pa < PAG_MAXPHYSPGS) + op10m_setz(w); /* Win, say phys mem is there */ + else + LRHSET(w, SBD_R12BLKNH, 0); /* Ugh, say no block */ + break; + + default: +#if KLH10_DEBUG + fprintf(stderr, "[io_sbdiag: Unimpl function: %#lo,,%#lo]\r\n", + (long)LHGET(w), (long)RHGET(w)); +#endif + op10m_setz(w); + break; + } + } else + op10m_setz(w); + + /* Store result */ + va_inc(e); /* Bump to E+1 */ + vm_write(e, w); /* Store result word in E+1 */ + return PCINC_1; +} + + +#endif /* KL */ + +#if KLH10_PAG_KL + +/* DEC TOPS-20 (KL) paging */ + +/* PAG_REFILL - Called when a virtual mem ref failed due to invalid +** access bits in the page map. Attempts a refill of the entry. +** On success, returns a vmptr_t to the physical memory location. +** On failure, depending on whether VMF_NOTRAP is set in the access flags, +** Not set - calls pag_fail() to carry out a page fault trap. +** Set - returns NULL. +** +** Note that if a MCA25 is defined to be present, what was formerly +** PF_VIRT becomes PF_KEEP. This change is not supported at the +** moment for failing refills; pag_t20map() does not report the +** access bits it found for failing references. +** However, there is nothing in the T20 monitor that tests for this. +*/ + +static vmptr_t pag_t20map(pment_t *, pagno_t, pment_t); + +vmptr_t +pag_refill(register pment_t *p, /* Page map pointer */ + vaddr_t e, /* Virtual address to map */ + pment_t f) /* Access flags */ +{ + register vmptr_t vmp; + register h10_t pflh; + + /* Note page number passed to pag_t20map is the FULL XA page, which on + ** a KL is 12+9=21 bits, not the supported 5+9=14 virtual. + */ + if (vmp = pag_t20map(p, va_xapage(e), (f & VMF_ACC))) /* Try mapping */ + return vmp + va_pagoff(e); /* Won, return mapped ptr! */ + + /* Ugh, analyze error far enough to build page fail word LH bits, + ** so that pag_fail can be invoked either now or later. + ** + ** Note that here is where we check for and set the APR condition bits + ** for (eg) NXM. If a PI is triggered (not a good idea, according to PRM + ** p.4-42), it will not take effect until + ** after the page fail trap has happened and control returns to the + ** main instruction loop. + */ + cpu.pag.pr_fmap = p; + cpu.pag.pr_facf = f; + switch (pflh = cpu.pag.pr_flh) { +#if KLH10_CPU_KS + case PMF_PARERR: /* Uncorrectable memory error */ + /* Phys addr set in pr_fref */ + cpu.aprf.aprf_set |= APRF_BMD; /* Report as APR flag too */ + apr_picheck(); /* Maybe set PI (will happen after pagefail) */ + pflh |= PF_VIRT; + break; + case PMF_NXMERR: /* Nonexistent memory */ + /* Phys addr set in pr_fref */ + cpu.aprf.aprf_set |= APRF_NXM; /* Report as APR flag too */ + apr_picheck(); /* Maybe set PI (will happen after pagefail) */ + pflh |= PF_VIRT; + break; + +#elif KLH10_CPU_KL + case PMF_ISCERR: /* Illegal section # */ + /* PRM 3-41 says don't set PF_VIRT for this error! */ + /* NOTE: Address part is undefined by PRM, but real KL seems to + ** report only 23 bits of virtual address. + */ + cpu.pag.pr_fref = va_30(e) & MASK23; /* Remember virt ref */ + break; + + /* Here we are using PMF_NXMERR to indicate not an ARX parity error + ** but a reference to non-existent physical memory, which sets + ** APR CONI bit 25 as well as bit 27. + ** The ERA (Error Address Register) also needs to be set. For now + ** just use the failing phys addr ref. + */ + case PMF_NXMERR: /* Nonexistent physical memory */ + /* Phys addr set in pr_fref, may have very high bits */ + cpu.pag.pr_era = cpu.pag.pr_fref; + cpu.aprf.aprf_set |= APRF_NXM | APRF_MBPAR; + apr_picheck(); /* Maybe set PI (will happen after pagefail) */ + pflh |= PF_VIRT; + break; +#endif + case PMF_OTHERR: /* Random inaccessibility (soft) */ + if (f & VMF_WRITE) + pflh |= PF_WREF; + /* Drop thru */ + case PMF_WRTERR: /* Write violation (soft) */ + default: +#if KLH10_CPU_KS + cpu.pag.pr_fref = va_insect(e); /* Remember virtual addr ref */ +#elif KLH10_CPU_KL + /* NOTE: real KL seems to report only 23 bits of virtual address. + */ + cpu.pag.pr_fref = va_30(e) & MASK23; /* Remember virtual ref */ +#endif + pflh |= PF_VIRT; + break; + } + if (p == cpu.pr_umap) + pflh |= PF_USER; + cpu.pag.pr_flh = pflh; /* Store complete flags back */ + + if (!(f & VMF_NOTRAP)) /* If caller isn't suppressing traps, */ + pag_fail(); /* take page failure trap now! */ + return NULL; /* Otherwise just return NULL */ +} + + +/* PAG_T20MAP - Perform address mapping. +** On success, returns vmptr_t to start of physical page. +** The summarized access bits in pr_flh will be used only by MAP. +** On failure, returns NULL. +** pr_flh in this case will contain a PMF_xxx value to be used by +** the refill code to build a page fail word. MAP ignores it. +** +** WARNING: No checks are made to see whether the CST base address has +** anything reasonable in it. This is a possible source of problems +** on the KL which can update the SPT or CST pointers anytime it likes, +** without special notification to the pager. +*/ +static vmptr_t +pag_t20map(register pment_t *p, /* Page map pointer */ + pagno_t vpag, /* Virtual Page # */ + pment_t f) /* Access flags */ +{ + register w10_t w; + register h10_t accbits = 0; + register paddr_t paddr; + register pagno_t pag, pgn; + + accbits = PT_WACC | PT_CACHE /* | PT_PACC */ ; /* Start with these */ + + /* Find which map to refill from. + ** Get appropriate section pointer (always 0 on KS) + */ + if (p == cpu.pr_umap) { /* Assume user map (most common) - UBR */ + paddr = cpu.mr_ubraddr + UPT_SC0; + } else if (p == cpu.pr_emap) { /* Else must be exec map - EBR */ + paddr = cpu.mr_ebraddr + UPT_SC0; + } else if (p == pr_pmap) { /* If phys map, trigger NXM */ + cpu.pag.pr_flh = PMF_NXMERR; /* NXM during phys ref */ + cpu.pag.pr_fref = pag_pgtopa(vpag); + cpu.pag.pr_fstr = "NXM with phys map"; + return NULL; /* Fail - non-ex memory in phys ref */ + } else + panic("pag_t20map: unknown page table! p=0x%lX, pg=%#lo", + (long)p, (long)vpag); + +#if KLH10_CPU_KL + if (vpag & ~PAG_NAMSK) { /* Non-zero section bits? */ + if (vpag >= PAG_MAXVIRTPGS) { + cpu.pag.pr_flh = PMF_ISCERR; /* Illegal Address */ + /* cpu.pag.pr_fref = 0; */ /* Set in pag_refill */ + cpu.pag.pr_fstr = "Illeg sect #"; + return NULL; /* Fail - Illegal virt address */ + } + paddr += (vpag >> (PAG_NABITS-PAG_BITS)); /* Get section # */ + pag = vpag & PAG_NAMSK; /* Find in-section page # */ + } + else +#endif + pag = vpag; /* Get page # for table lookup */ + + for (;;) { + if (paddr >= cpu.pag.pr_physnxm) { + cpu.pag.pr_flh = PMF_NXMERR; + cpu.pag.pr_fref = paddr; + cpu.pag.pr_fstr = "NXM fetching sect ptr"; + return NULL; /* Fail - NXM fetching section pointer */ + } + w = vm_pget(vm_physmap(paddr)); /* Get section pointer */ + + switch (LHGET(w) >> 15) { /* Find pointer type */ + default: + case PTT_NOACC: + cpu.pag.pr_flh = PMF_OTHERR; + cpu.pag.pr_fstr = "noacc sect ptr"; + return NULL; /* Fail - inaccessible pointer */ + + case PTT_IMMED: + accbits &= LHGET(w); /* Mask access bits (P,W,C) */ + break; /* Won, drop out with ptr to map */ + + case PTT_SHARED: + accbits &= LHGET(w); /* Mask access bits (P,W,C) */ + paddr = PAG_PR_SPBPA + RHGET(w); /* Find SPT ent addr */ + if (paddr >= cpu.pag.pr_physnxm) { + cpu.pag.pr_flh = PMF_NXMERR; + cpu.pag.pr_fref = paddr; + cpu.pag.pr_fstr = "NXM for sect SPT ent"; + return NULL; /* Fail - NXM fetching SPT entry */ + } + w = vm_pget(vm_physmap(paddr)); /* Get SPT entry */ + break; /* Won, have page addr */ + + case PTT_INDIRECT: + /* DEC PRM p.3-35 and p.4-22 says "Memory status is kept only + ** for the page maps." i.e. not for section table pages. So any + ** indirect section pointers generate no CST checks/updates. + */ + accbits &= LHGET(w); /* Mask access bits (P,W,C) */ + pgn = LHGET(w) & PT_IIDX; /* Save idx into next table */ + paddr = PAG_PR_SPBPA + RHGET(w); /* Find SPT ent addr */ + if (paddr >= cpu.pag.pr_physnxm) { + cpu.pag.pr_flh = PMF_NXMERR; + cpu.pag.pr_fref = paddr; + cpu.pag.pr_fstr = "NXM for @ sect SPT ent"; + return NULL; /* Fail - NXM fetching SPT entry */ + } + w = vm_pget(vm_physmap(paddr)); /* Get SPT entry */ + if (LHGET(w) & SPT_MDM) { + cpu.pag.pr_flh = PMF_OTHERR; + cpu.pag.pr_fstr = "non-core sect map"; + return NULL; /* Fail - inaccessible page map */ + } + paddr = pag_pgtopa(RHGET(w) & SPT_PGN) + pgn; + continue; /* Loop to get next section pointer */ + } + break; /* Break from switch is break from loop */ + } + + /* Now have the page address of the page map to use! + ** w contains the final pointer word. + ** pag is the page # to look up in the page map. + ** accbits has the access bits so far. + */ + for (;;) { + if (LHGET(w) & SPT_MDM) { + cpu.pag.pr_flh = PMF_OTHERR; + cpu.pag.pr_fstr = "non-core page map"; + return NULL; /* Fail - inaccessible page map */ + } + pgn = RHGET(w) & SPT_PGN; + + /* Check page # here before possibly clobbering CST */ + if (pgn > (PAG_MAXPHYSPGS-1)) { + cpu.pag.pr_flh = PMF_NXMERR; + cpu.pag.pr_fref = pag_pgtopa(pgn) + pag; + cpu.pag.pr_fstr = "map in NX page"; + return NULL; /* Fail - map in non-ex page */ + } + /* Carry out CST update if CST exists. */ + if (PAG_PR_CSBPA) { + w = vm_pget(vm_physmap(PAG_PR_CSBPA + pgn)); /* Get CST */ + if ((LHGET(w) & CST_AGE) == 0) { + cpu.pag.pr_flh = PMF_OTHERR; + cpu.pag.pr_fstr = "map age trap"; + return NULL; /* Fail - age trap */ + } + op10m_and(w, PAG_PR_CSTMWD); /* AND it with CSTM */ + op10m_ior(w, PAG_PR_PURWD); /* IOR with PUR */ + vm_pset(vm_physmap(PAG_PR_CSBPA+pgn), w); /* Store back in CST */ + } + + /* OK, now pluck out page map entry */ + paddr = pag_pgtopa(pgn) + pag; + w = vm_pget(vm_physmap(paddr)); /* Get page map entry */ + + /* Handle map pointer */ + switch (LHGET(w) >> 15) { /* Get map pointer type */ + default: + case PTT_NOACC: + cpu.pag.pr_flh = PMF_OTHERR; + cpu.pag.pr_fstr = "noacc page ptr"; + return NULL; /* Fail - inaccessible pointer */ + + case PTT_IMMED: + accbits &= LHGET(w); /* Mask access bits (P,W,C) */ + break; /* Won, drop out with ptr to page */ + + case PTT_SHARED: + accbits &= LHGET(w); /* Mask access bits (P,W,C) */ + paddr = PAG_PR_SPBPA + RHGET(w); /* Find SPT ent addr */ + if (paddr >= cpu.pag.pr_physnxm) { + cpu.pag.pr_flh = PMF_NXMERR; + cpu.pag.pr_fref = paddr; + cpu.pag.pr_fstr = "NXM for page SPT ent"; + return NULL; /* Fail - NXM fetching SPT entry */ + } + w = vm_pget(vm_physmap(paddr)); /* Get SPT entry */ + break; /* Won, drop out with page addr */ + + case PTT_INDIRECT: + /* Note these effect CST update for secondary page maps by looping. + */ + accbits &= LHGET(w); /* Mask access bits (P,W,C) */ + pag = LHGET(w) & PT_IIDX; /* Clobber page # with index! */ + paddr = PAG_PR_SPBPA + RHGET(w); /* Find SPT ent addr */ + if (paddr >= cpu.pag.pr_physnxm) { + cpu.pag.pr_flh = PMF_NXMERR; + cpu.pag.pr_fref = paddr; + cpu.pag.pr_fstr = "NXM for @ page SPT ent"; + return NULL; /* Fail - NXM fetching SPT entry */ + } + w = vm_pget(vm_physmap(paddr)); /* Get SPT entry */ + continue; /* Loop to handle it */ + } + break; + } + + /* Now have final page address */ + if (LHGET(w) & SPT_MDM) { /* Check storage medium field */ + cpu.pag.pr_flh = PMF_OTHERR; + cpu.pag.pr_fstr = "non-core page"; + return NULL; /* Fail - inaccessible pointer to page */ + } + pag = RHGET(w) & SPT_PGN; + + /* Check page # here before possibly clobbering CST */ + if (pag > (PAG_MAXPHYSPGS-1)) { + cpu.pag.pr_flh = PMF_NXMERR; + cpu.pag.pr_fref = pag_pgtopa(pag); /* Don't know offset here */ + cpu.pag.pr_fstr = "NX phy page"; + return NULL; /* Fail - non-ex page # */ + } + + /* Carry out CST update if CST exists */ + if (PAG_PR_CSBPA) { + w = vm_pget(vm_physmap(PAG_PR_CSBPA + pag)); /* Get CST */ + if ((LHGET(w) & CST_AGE) == 0) { + cpu.pag.pr_flh = PMF_OTHERR; + cpu.pag.pr_fstr = "page age trap"; + return NULL; /* Fail - age trap */ + } + op10m_and(w, PAG_PR_CSTMWD); /* AND it with CSTM */ + op10m_ior(w, PAG_PR_PURWD); /* IOR with PUR */ + + /* Last check - attempted write reference? If so, may need to + ** also set M bit in CST entry. + */ + if (accbits & PT_WACC) { /* If page is writable */ + if (f & VMF_WRITE) /* and this is write ref */ + op10m_iori(w, CST_MBIT); /* then force the M bit */ + accbits |= RHGET(w) & CST_MBIT; /* Turn on M if set in CST */ + /* (note symbol mix with PT_xxx) */ + } else if (f & VMF_WRITE) { + /* Page not writable, but trying to do a write ref. Fail. */ + vm_pset(vm_physmap(PAG_PR_CSBPA+pag), w); + cpu.pag.pr_flh = PMF_WRTERR; + cpu.pag.pr_fstr = "W in RO Page"; + return NULL; /* Fail, page not writable */ + } + vm_pset(vm_physmap(PAG_PR_CSBPA+pag), w); /* Store back in CST */ + + } else { + /* CST doesn't exist (this can happen for TOPS-10 using T20 paging). + ** Not clear what to do about reporting the M bit. + ** Currently the hardware page table emulation doesn't retain such + ** a bit, in order to keep the entry size at 16 bits. So we'll + ** fake it for now by pretending that M is always set if W is; + ** i.e. all writable pages are assumed to be modified. + ** And let's hope nobody pays attention to it. T20 at least + ** doesn't appear to. + ** Note: error msg is deliberately worded differently from CST + ** case, to maintain uniqueness of all messages. Helps debug. + */ + if (accbits & PT_WACC) { /* If page is writable */ + accbits |= CST_MBIT; /* then always turn on M */ + /* (note symbol mix with PT_xxx) */ + } else if (f & VMF_WRITE) { + /* Page not writable, but trying to do a write ref. Fail. */ + cpu.pag.pr_flh = PMF_WRTERR; + cpu.pag.pr_fstr = "W of RO page"; + return NULL; /* Fail, page not writable */ + } + } + cpu.pag.pr_flh = accbits; /* Remember bits in case MAP */ + + /* Won completely, update internal page map! + ** VMF_READ serves the function of a 'valid' bit, and + ** VMF_WRITE serves as the 'M' (modified) bit. M should be set only + ** if the page is writable *and* M is set in the CST. + ** There is no need or support for an internal 'C' (cacheable) bit. + ** Likewise for any other bits (P, K) at the moment. + */ + p[vpag] = pag | VMF_READ + | ((accbits & CST_MBIT) ? VMF_WRITE : 0); + + return vm_physmap(pag_pgtopa(pag)); +} + +#endif /* T20 (KL) paging */ + +#if KLH10_EXTADR + +/* PAG_IISET - Called to set up for taking an Illegal Indirection page fail. +** PAG_IIFAIL - Called from effective address calculation if an +** illegal indirect word is seen (bits 0,1 == 11). +** The PRM says nothing about what the page fail word contains for +** this case; what I'll do is provide E, where c(E) contains the +** bad indirect word. This appears to be correct, according to the +** DFKED diagnostics. +** The PRM also says nothing about the handling of this when not +** paging! +*/ +void +pag_iiset(vaddr_t e, + pment_t *map) +{ + cpu.pag.pr_flh = (PMF_IIERR | PF_VIRT + | (map == cpu.pr_umap ? PF_USER : 0) + ); + cpu.pag.pr_fref = va_30(e); + cpu.pag.pr_fstr = "Illeg Indir"; + cpu.pag.pr_fmap = map; + cpu.pag.pr_facf = 0; +} + +void +pag_iifail(vaddr_t e, + pment_t *map) +{ + pag_iiset(e, map); + pag_fail(); /* What to do if not paging? */ +} + +#endif /* KLH10_EXTADR */ + +/* PMOVE, PMOVEM. Sort of related to paging, so put here. +*/ + +#if KLH10_CPU_KLX + +/* PMOVE - [op 052] Move from physical memory address (late KL addition) +** c(E) contains physical address of word to fetch into AC. +*/ +insdef(i_pmove) +{ + register w10_t w; + register paddr_t pa; + + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) /* Standard IO-legal test */ + return i_muuo(op, ac, e); + + w = vm_read(e); + pa = ((paddr_t)LHGET(w)<<18) | RHGET(w); + if (pa >= ((paddr_t)PAG_MAXPHYSPGS << PAG_BITS)) { + /* Non-ex phys mem ref, do page-fail */ + pag_nxmfail(pa, VMF_READ, "PMOVE NXM"); + op10m_setz(w); /* If no trap, read 0 */ + ac_set(ac, w); + } else + ac_set(ac, vm_pget(vm_physmap(pa))); /* Fetch c(PA) into AC */ + + return PCINC_1; +} + +/* PMOVEM - [op 053] Move to physical memory address (late KL addition) +** c(E) contains physical address of word to store AC into. +*/ +insdef(i_pmovem) +{ + register w10_t w; + register paddr_t pa; + + if (PCFTEST(PCF_USR) && !PCFTEST(PCF_UIO)) /* Standard IO-legal test */ + return i_muuo(op, ac, e); + + w = vm_read(e); + pa = ((paddr_t)LHGET(w)<<18) | RHGET(w); + if (pa >= ((paddr_t)PAG_MAXPHYSPGS << PAG_BITS)) { + /* Non-ex phys mem ref, do page-fail */ + pag_nxmfail(pa, VMF_WRITE, "PMOVEM NXM"); + /* If no trap, just do nothing */ + } else + vm_pset(vm_physmap(pa), ac_get(ac)); /* Store AC in c(PA) */ + + return PCINC_1; +} + +#endif /* KLH10_CPU_KLX */ + + +/* PAG_NXMFAIL - Handle physical memory map NXM error, eg if paging off. +** +** What to do if something like PMOVE/M references non-existent phys memory? +** There's no doc that says anything about this. +** What I'll do here is take a page failure trap of type PMF_NXMERR, which +** is the right thing on the KS and seems close enough on the KL. +** The APR bits set differ, however. +** Based on the MCA25 doc (p.A-8), bit 0 is also set if in user mode. +** See T20 pag_refill() for similar code. +** Note: for KL with KI paging, the DFKDA diagnostic expects code 36, +** "AR Parity", for a NXM error. +*/ +static void +pag_nxmfail(paddr_t pa, + pment_t f, /* Access flags */ + char *str) +{ + cpu.pag.pr_fmap = pr_pmap; /* Physical map */ + cpu.pag.pr_facf = f; + cpu.pag.pr_fref = pa; /* Phys failure addr */ + cpu.pag.pr_fstr = str; /* Failure string for debugging */ + +#if KLH10_PAG_ITS /* ITS/KS: just a guess */ + cpu.pag.pr_flh = PF_NXM | PF_PHY | (PCFTEST(PCF_USR) ? PF_USR : 0); +#else /* KS: Non-ex memory, KL: AR parity */ + cpu.pag.pr_flh = PMF_NXMERR | (PCFTEST(PCF_USR) ? PF_USER : 0); +#endif + + cpu.aprf.aprf_set |= +#if KLH10_CPU_KS + APRF_NXM; /* Report as APR flag too (B27) */ +#elif KLH10_CPU_KL + APRF_NXM | APRF_MBPAR; /* KL has different flags */ + cpu.pag.pr_era = pa; /* Also remember phys addr here */ +#endif + + apr_picheck(); /* Maybe set PI (will happen after pagefail) */ + if (cpu.mr_paging) /* If paging, */ + pag_fail(); /* Take page failure trap now! */ + + /* Ugh!! Return to caller, who should carry out some default */ + if (cpu.mr_exsafe) { + pfbeg(); /* Barf about it */ + fprintf(stderr, "Paging OFF, no trap!]\r\n"); + if (cpu.mr_exsafe >= 2) + apr_halt(HALT_EXSAFE); + } +} diff --git a/src/kn10pag.h b/src/kn10pag.h new file mode 100644 index 0000000..3e085ca --- /dev/null +++ b/src/kn10pag.h @@ -0,0 +1,973 @@ +/* KN10PAG.H - Pager state and register definitions +*/ +/* $Id: kn10pag.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: kn10pag.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#ifndef KN10PAG_INCLUDED +#define KN10PAG_INCLUDED 1 + +#ifdef RCSID + RCSID(kn10pag_h,"$Id: kn10pag.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Address space definitions, in terms of bits */ + +/* Naming note: +** Use: For: +** PA Physical Address. +** NA "iN-section" or "Normal" 18-bit Address. +** XA full eXtended virt Address (30-bit). +** VA Virtual Address actually supported (18 or 23-bit). +** +** XS full eXtended Section # (12-bit) +** VS Virtual Section # actually supported (5-bit) +*/ + +#ifndef PAG_PABITS /* # bits of address physically available */ +# if KLH10_CPU_KLX +# define PAG_PABITS 22 /* 22 MAX! Can set less for debugging */ +# else +# define PAG_PABITS 19 /* True for KS10 anyway */ +# endif +#endif + +#define PAG_NABITS 18 /* Normal in-section virtual address bits */ +#define PAG_XSBITS 12 /* # bits virtual section possible */ +#define PAG_XABITS 30 /* # virtual bits possible in extended mode (18+12) */ + +#if KLH10_CPU_KLX +# define PAG_VSBITS 5 /* # bits virtual section actually supported */ +# define PAG_MAXBITS PAG_XABITS /* Max address of any kind */ +#else +# define PAG_VSBITS 0 /* # bits virtual section actually supported */ +# define PAG_MAXBITS PAG_PABITS /* Max address of any kind */ +#endif + + /* # virtual address bits actually supported! (18 or 23) */ +#define PAG_VABITS (PAG_VSBITS+PAG_NABITS) + + +#if KLH10_PAG_ITS +# define PAG_BITS 10 /* # bits of address encompassed by a page */ +#else +# define PAG_BITS 9 +#endif + +#define PAG_VMFBITS 2 /* # of access bits needed in page table */ + /* (See VMF_ flags) */ + +/* The remaining definitions are more or less all derived +** from the above parameters. +** Note some of the defs should be unsigned but aren't, as they are +** used in some #if tests and some preprocs don't like 1UL. +** Still guaranteed positive since long is >= 32 bits. +*/ + +/* Max # of pages the KLH10 emulates in physical memory. +*/ +#ifndef PAG_MAXPHYSPGS +# define PAG_MAXPHYSPGS (1L<<(PAG_PABITS-PAG_BITS)) +#endif + +/* Max # of virtual pages this pager will support. +*/ +#define PAG_MAXVIRTPGS (1L<<(PAG_VABITS-PAG_BITS)) + +/* # bits needed in a page table entry. +** Only have to support available phys mem. +*/ +#define PAG_PMEBITS (PAG_VMFBITS+PAG_PABITS-PAG_BITS) + + +/* Now declare three types: +** paddr_t - fast type big enough to hold the biggest phys/virt address. +** pagno_t - fast type big enough to hold a physical page #. +** pment_t - compact type for holding a hardware page table entry. +** +** For explanation of the #if tests, see similar code in word10.h that +** defines WORD10_INT18. +*/ +#ifndef INT_MAX +# include +#endif + +/* paddr_t - Physical address (always integral type) +** This is a macro instead of the typedef it should be, because +** on some Nazi U-boxes it is cleverly typedefed in , and +** C has no way to undefine (or even test) for such things. +*/ +#ifndef KLH10_PADDR_T /* Permit compile-time override */ +# if (INT_MAX > (1L<<(PAG_MAXBITS-2))) +# define KLH10_PADDR_T int /* Assume int is fastest if it's big enough */ +# else +# define KLH10_PADDR_T long +# endif +#endif +/* typedef unsigned KLH10_PADDR_T paddr_t; */ +#define paddr_t unsigned KLH10_PADDR_T + +#ifndef KLH10_PAGNO_T /* Permit compile-time override */ +# if (INT_MAX > (PAG_MAXPHYSPGS>>2)) +# define KLH10_PAGNO_T int +# else +# define KLH10_PAGNO_T long +# endif +#endif + typedef unsigned KLH10_PAGNO_T pagno_t; + +#ifndef KLH10_PMENT_T /* Permit compile-time override */ +# if (SHRT_MAX > (1L<<(PAG_PMEBITS-2))) +# define KLH10_PMENT_T short +# elif (INT_MAX > (1L<<(PAG_PMEBITS-2))) +# define KLH10_PMENT_T int +# else +# define KLH10_PMENT_T long +# endif +#endif + typedef unsigned KLH10_PMENT_T pment_t; + + +#define PAG_SIZE (1<>PAG_BITS)&PAG_PAMSK) /* Phy to page */ +#define pag_pgtopa(p) (((paddr_t)(p)) << PAG_BITS) /* Page to phy */ + +/* To extract page # from given virtual address, +** see va_page(va) and va_xapage(va). +*/ + +/* Virtual Address Facilities (includes EA calculation) +** +** vaddr_t is the object type used to store a full PDP-10 +** virtual address. This is NOT the same as a physical +** address and may not even be an integral type. +** +** Both extended and non-extended definitions are integrated here. +** There are complicated enough however that perhaps a separate file +** would be a good idea (vaddr.h?). +*/ + + typedef int32 vaddr_t; /* Later make unsigned */ + typedef int32 vaint_t; /* For future code */ + typedef uint32 vauint_t; + +#if !KLH10_EXTADR + +# define va_lh(va) ((va) >> H10BITS) +# define va_ac(va) ((va) & AC_MASK) +# define va_sect(va) (0) +# define va_sectf(va) (0) +# define va_insect(va) ((va) & H10MASK) +# define va_pagoff(va) ((va) & PAG_MASK) /* Get offset within page */ +# define va_page(va) ((pagno_t)((va)>>PAG_BITS)&PAG_VAMSK) /* Get page # */ +# define va_xapage(va) ((pagno_t)((va)>>PAG_BITS)&PAG_XAMSK) /* FULL page!! */ +# define va_30(va) (va) + +# define va_ladd(va,n) ((va) = ((va)+(n))&H10MASK) +# define va_linc(va) va_ladd(va,1) /* Local increment by 1 */ +# define va_add(va,n) va_ladd(va,n) /* General same as local */ +# define va_inc(va) va_linc(va) /* Ditto */ +# define va_dec(va) va_ladd(va,-1) + +# define va_lmake(va, s, n) ((va) = (n)) +# define va_gmake(va, s, n) ((va) = (n)) +# define va_lmake30(va, pa) ((va) = (pa)) +# define va_hmake(va, lh, rh) ((va) = ((lh)<>1)) +#define VAF_GLOBAL VAF_LGFLAG +#define VAF_LOCAL 0 + +#define VAF_NBITS PAG_NABITS /* 18 bits in-section */ +#define VAF_SBITS PAG_XSBITS /* 12 bits section # */ + /* (Even though pager may only support 5) */ +#define VAF_VSBITS PAG_VSBITS /* # bits virtual section possible */ +#define VAF_SPOS VAF_NBITS + +#define VAF_SMSK (((vauint_t)1<> H10BITS)&H10MASK) +# define va_ac(va) ((va) & AC_MASK) +# define va_sect(va) (((va)>>VAF_SPOS)&VAF_SMSK) +# define va_sectf(va) ((va) & VAF_SFLD) +# define va_insect(va) ((va) & H10MASK) +# define va_pagoff(va) ((va) & PAG_MASK) /* Get offset within page */ +# define va_page(va) ((pagno_t)((va)>>PAG_BITS)&PAG_VAMSK) /* Get page # */ +# define va_xapage(va) ((pagno_t)((va)>>PAG_BITS)&PAG_XAMSK) /* FULL page!! */ +# define va_30(va) ((va) & VAF_30MSK) + +# define va_ladd(va,n) ((va) = (((va)&~VAF_NFLD)|(((va)+(n))&VAF_NFLD))) +# define va_linc(va) va_ladd(va,1) /* Local increment by 1 */ +# define va_add(va,n) (va_islocal(va) ? va_ladd(va,n) : va_gadd(va,n)) +# define va_inc(va) (va_islocal(va) ? va_linc(va) : va_ginc(va)) +# define va_dec(va) (va_islocal(va) ? va_ladd(va,-1) : va_gadd(va,-1)) + +# define va_lmake(va, s, n) ((va) = ((s)< User low mem ( 0-377777) + DBR2 -> User high (400000-777777) + DBR3 -> EXEC high + DBR4 -> EXEC low (didn't exist on KA-10s) +*/ + +/* Page table entry format */ +#define PM_29 0400000 /* Bit 2.9 of ITS map entry halfword */ +#define PM_28 0200000 /* Bit 2.8 */ + +#define PM_ACC (PM_29|PM_28) /* Mask for ITS access bits: */ + /* Note: h/w treats RWF the same as RO. */ +#define PM_ACCNON ((h10_t)00<<16) /* 00 - invalid, inaccessible */ +#define PM_ACCRO ((h10_t)01<<16) /* 01 - Read Only */ +#define PM_ACCRWF ((h10_t)02<<16) /* 10 - Read/Write/First (== RO) */ +#define PM_ACCRW ((h10_t)03<<16) /* 11 - Read/Write */ + +#define PM_AGE 020000 /* Age bit */ +#define PM_CSH 010000 /* Cache enable bit */ +#define PM_PAG PAG_PAMSK /* Physical ITS page number (max 1777?) */ +#if (PM_PAG & PM_CSH) + * ERROR * "ITS page number field too large" +#endif + + +/* Format of ITS page fail word */ +#define PF_USR 0400000 /* 4.9 Indicates user address space. */ +#define PF_NXI 0200000 /* 4.8 Nonexistent IO register. */ +#define PF_NXM 0100000 /* 4.7 Nonexistent memory. */ +#define PF_PAR 0040000 /* 4.6 Uncorrectable memory error. */ + /* (AC0 in block 7 has the word unless 4.7 is */ + /* also set.) */ + /* 4.5 unused */ +#define PF_WRT 0010000 /* 4.4 Soft fault reference called for writing. */ +#define PF_29 004000 /* 4.3 - 4.2 Access bits for referenced page in soft */ +#define PF_28 002000 /* fault. (from 2.9 & 2.8 of page entry) */ +#define PF_PHY 0001000 /* 4.1 Address given was physical. */ + /* 3.9 unused */ +#define PF_IO 0000200 /* 3.8 Indicates an IO operation. */ + /* 3.7-3.6 unused */ +#define PF_BYT 0000020 /* 3.5 Indicates a byte IO operation. */ + /* 3.4 - 1.1 IO address */ + /* or */ + /* 3.1 - 1.1 Memory address */ +#define PF_PNO 0776000 /* 2.9 - 2.2 Virtual page number */ + + +/* Format of the area read and written by LPMR, SPM: */ +#define LPMR_DBR1 0 /* User low DBR */ +#define LPMR_DBR2 1 /* User high DBR */ +#define LPMR_QUANT 2 /* Quantum timer */ +#define LPMR_UJPC 3 /* User JPC (if supported) */ +#define LPMR_EJPC 4 /* Exec JPC (if supported) */ + +/* The quantum timer appears to be incremented approximately every +** millisecond (2^12 the KS10 internal clock rate) whenever +** not holding an interrupt (ie no PI in progress). +*/ + +/* Paging register variables */ +struct pagregs { + int32 pr_dbr1, pr_dbr2, pr_dbr3, pr_dbr4; /* DBR regs */ + int32 pr_quant; + vaddr_t pr_ujpc, pr_ejpc; + + /* Common to all systems */ + h10_t pr_flh; /* Fail word LH bits */ + paddr_t pr_fref; /* Failing virtual/physical address */ + char *pr_fstr; /* Failure reason (for debugging) */ + pment_t *pr_fmap; /* Failing map pointer */ + h10_t pr_facf; /* Failing ref access bits */ +}; +#endif /* ITS */ + +/* T10 (KI) Pager definitions */ + +#if KLH10_PAG_KI + +/* Page table entry format */ +#define PM_ACC 0400000 /* B0 - Access allowed (R or RW) */ +#define PM_PUB 0200000 /* B1 - Public (not used in KS10) */ +#define PM_WRT 0100000 /* B2 - Writable */ +#define PM_SFT 0040000 /* B3 - Software (not used by page refill) */ +#define PM_CSH 0020000 /* B4 - Cacheable */ +#define PM_PAG 0017777 /* Physical DEC page number - 13 bits */ +#if (PM_PAG < PAG_PAMSK) + /*#*/ * ERROR * "T10 page number field too small" +#endif + +/* Page fail bits as returned by MAP or page failure trap */ +#define PF_USER ((h10_t)1<<17) /* B0 - User space reference (else exec) */ +#define PF_HARD ((h10_t)1<<16) /* B1 - Hardware or IO failure (else soft) */ +#define PF_ACC ((h10_t)1<<15) /* B2 - 'Access' bit from page map entry */ +#define PF_WRT (1<<14) /* B3 - 'Writable' bit from page map entry */ +#define PF_SFT (1<<13) /* B4 - 'Soft' bit from page map entry */ +#define PF_WREF (1<<12) /* B5 - Reference was write */ +#define PF_PUB (1<<11) /* B6 - 'Public' bit from refill eval */ +#define PF_CSH (1<<10) /* B7 - 'Cacheable' bit from refill eval */ +#define PF_VIRT (1<<9) /* B8 - Physical or Virtual reference */ + /* (1<<8) */ /* B9 - */ +#define PF_IOREF (1<<7) /* B10 - IO Bus reference */ + /* (1<<6) (1<<5) */ /* B11,B12 - */ +#define PF_IOBYTE (1<<4) /* B13 - Byte mode in failing IO ref */ + +#if KLH10_CPU_KL /* Sufficient for now, later merge */ +# define PMF_IIERR ((h10_t)024<<12) /* Illegal Indirect (EFIW) */ +# define PMF_NXMERR ((h10_t)036<<12) /* AR parity error (pretend NXM) */ +#endif /* DFKDA diag expects this */ + + +struct pagregs { +#if KLH10_CPU_KL + int pr_pcs; /* Previous Context Section */ + paddr_t pr_pcsf; /* PCS, shifted into field position */ + paddr_t pr_era; /* Error Address Register */ +#endif /* KL */ + + paddr_t pr_physnxm; /* 1st physical NXM address */ + + /* Common to all systems */ + h10_t pr_flh; /* Page fail code, or refill access bits */ + paddr_t pr_fref; /* If phys failure, holds phys addr of ref */ + char *pr_fstr; /* Failure string for debugging */ + pment_t *pr_fmap; /* Failing hardware map pointer */ + h10_t pr_facf; /* Failing ref access bits */ +}; + +#endif /* KLH10_PAG_KI */ + +/* T20 (KL) Pager definitions */ + +#if KLH10_PAG_KL + +/* Bits for page map entry LH as returned by MAP or page failure. +** Source for this is DEC PRM p.4-26 to p.4-28. +** Same bits for KL, PRM p.3-40. +*/ +#define PF_USER ((h10_t)1<<17) /* B0 U - User space reference (else exec) */ +#define PF_HARD ((h10_t)1<<16) /* B1 H - Hardware or IO failure (else soft) */ +#define PF_ACC (1<<15) /* B2 A - Accessible, refill eval completed */ +#define PF_MOD (1<<14) /* B3 M - 'Modified' bit from h/w page table */ +#define PF_WRT (1<<13) /* B4 S - 'Writable' bit from refill eval */ +#define PF_WREF (1<<12) /* B5 T - Reference was write */ +#define PF_PUB (1<<11) /* B6 P - 'Public' bit from refill eval */ +#define PF_CSH (1<<10) /* B7 C - 'Cacheable' bit from refill eval */ +#define PF_VIRT (1<<9) /* B8 V - Physical or Virtual reference */ +#if KLH10_MCA25 +# define PF_KEEP (1<<9) /* B8 K - Keep page during WRUBR */ +#endif + +#if KLH10_CPU_KS + /* (1<<8) */ +# define PF_IOREF (1<<7) /* B10 - IO Bus reference */ + /* (1<<6) (1<<5) */ +# define PF_IOBYTE (1<<4) /* B13 - Byte mode in failing IO ref */ +#endif /* KS */ + +/* Special hardware failure codes in high 6 bits. */ +#if KLH10_CPU_KS +# define PMF_PARERR ((h10_t)036<<12) /* Uncorrectable memory error */ +# define PMF_NXMERR ((h10_t)037<<12) /* Nonexistent memory */ +# define PMF_IOERR ((h10_t)020<<12) /* Nonexistent IO register */ +#elif KLH10_CPU_KL +# define PMF_PUBERR ((h10_t)021<<12) /* Proprietary violation */ +# define PMF_ADRERR ((h10_t)023<<12) /* Address break */ +# define PMF_IIERR ((h10_t)024<<12) /* Illegal Indirect (EFIW) */ +# define PMF_PGTERR ((h10_t)025<<12) /* Page table parity error */ +# define PMF_ISCERR ((h10_t)027<<12) /* Illegal Address (sect > 037) */ +# define PMF_NXMERR ((h10_t)036<<12) /* AR parity error (pretend NXM) */ +# define PMF_ARXERR ((h10_t)037<<12) /* ARX parity error */ +#endif +#define PMF_WRTERR ((h10_t)011<<12) /* Write violation (soft) */ +#define PMF_OTHERR ((h10_t)000<<12) /* Random inaccessibility (soft) */ + +/* Section and Map pointer format */ +#define PT_PACC (1<<14) /* B3 - 'P' bit, public */ +#define PT_WACC (1<<13) /* B4 - 'W' bit, writeable */ +#define PT_KEEP (1<<12) /* B5 - 'K' bit, Keep (KL MCA25) */ +#define PT_CACHE (1<<11) /* B6 - 'C' bit, cacheable */ +#define PT_TYPE ((h10_t)07<<15) /* High 3 bits are pointer type */ +#define PTT_NOACC 0 /* Inaccessible pointer */ +#define PTT_IMMED 1 /* Immediate pointer (see SPT fields) */ +#define PTT_SHARED 2 /* Shared pointer */ + /* RH: Index into SPT */ +#define PTT_INDIRECT 3 /* Indirect pointer */ +#define PT_IIDX 0777 /* LH: B9-17 Next index */ + /* RH: Index into SPT (like shared) */ + +/* Format of SPT (Special Page Table) entries. +** The "Page Address" defined here is also used in +** immediate section/map pointers (type PTT_IMMED). +*/ +#define SPT_MDM 077 /* LH: B12-17 Storage medium (0=mem) */ +#define SPT_RSVD 0760000 /* RH: B18-22 Reserved */ +#define SPT_PGN 0017777 /* RH: B23-35 Page Number */ + +/* Format of CST (Core Status Table) entry. +** There is one entry for every physical page on the machine. +*/ +#define CST_AGE 0770000 /* LH: Page age - if zero, trap on ref. */ +#define CST_HWBITS 017 /* RH: Hardware bits, s/w shouldn't hack */ +#define CST_MBIT 01 /* RH: referenced page modified */ + +/* Paging registers +** Note that on the KL the "registers" are stored in AC block 6. +** Assumption is that this is NEVER the current ac block; known +** to be true for T20 monitor at least. +*/ +struct pagregs { +#if KLH10_CPU_KS + paddr_t pr_spb; /* SPT Base address (not aligned, unknown size) */ + paddr_t pr_csb; /* CST Base address (not aligned, # = phys pages) */ + w10_t pr_cstm; /* CST Mask word */ + w10_t pr_pur; /* CST Process Use Register */ +# define PAG_PR_SPBPA cpu.pag.pr_spb +# define PAG_PR_CSBPA cpu.pag.pr_csb +# define PAG_PR_CSTMWD cpu.pag.pr_cstm +# define PAG_PR_PURWD cpu.pag.pr_pur +#elif KLH10_CPU_KL +# define PAG_PR_SPB (cpu.acblks[6][3]) +# define PAG_PR_CSB (cpu.acblks[6][2]) +# define PAG_PR_SPBPA (((paddr_t)LHGET(PAG_PR_SPB)<<18) \ + | RHGET(PAG_PR_SPB)) +# define PAG_PR_CSBPA (((paddr_t)LHGET(PAG_PR_CSB)<<18) \ + | RHGET(PAG_PR_CSB)) +# define PAG_PR_CSTMWD (cpu.acblks[6][0]) +# define PAG_PR_PURWD (cpu.acblks[6][1]) + int pr_pcs; /* Previous Context Section */ + paddr_t pr_pcsf; /* PCS, shifted into field position */ + paddr_t pr_era; /* Error Address Register */ +#endif /* KL */ + + paddr_t pr_physnxm; /* 1st physical NXM address */ + + /* Common to all systems */ + h10_t pr_flh; /* Page fail code, or refill access bits */ + paddr_t pr_fref; /* If phys failure, holds phys addr of ref */ + char *pr_fstr; /* Failure string for debugging */ + pment_t *pr_fmap; /* Failing map pointer */ + h10_t pr_facf; /* Failing ref access bits */ +}; + +#endif /* KLH10_PAG_KL */ + +#if KLH10_CPU_KL +# define pag_pcsget() cpu.pag.pr_pcs +# define pag_pcsfget() cpu.pag.pr_pcsf +# define pag_pcsset(s) (cpu.pag.pr_pcsf = ((paddr_t)(s)<<18),\ + cpu.pag.pr_pcs = (s)) +#endif + +/* Pager function declarations - exported from kn10pag.c */ + +extern void pag_init(void); /* Initialize pager stuff */ + + /* Called if vm_xmap mapping macros fail. Refill, trap if page-fail */ +extern vmptr_t pag_refill(pment_t *, vaddr_t, pment_t); + +extern void pag_fail(void); /* Effect page-fail trap */ + +#if KLH10_CPU_KS +extern void pag_iofail(paddr_t, int); /* PF trap for IO unibus ref */ +#endif + +#if KLH10_EXTADR /* PF trap for Illegal-Indirect ref */ +extern void pag_iifail(vaddr_t, pment_t *); +extern void pag_iiset(vaddr_t, pment_t *); /* Set up vars for above */ +#endif + +#endif /* ifndef KN10PAG_INCLUDED */ diff --git a/src/opcods.h b/src/opcods.h new file mode 100644 index 0000000..df48b4d --- /dev/null +++ b/src/opcods.h @@ -0,0 +1,794 @@ +/* OPCODS.H - Definitions of all PDP-10 instruction opcodes +*/ +/* $Id: opcods.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: opcods.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#ifndef OPCODS_RCSID +# define OPCODS_RCSID \ + RCSID(opcods_h,"$Id: opcods.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* This file is intended to be included multiple times, always within +** a specific context that defines the macros "idef", "ixdef", and "iodef" +** suitably so as to declare or define various parts of the bindings +** represented in this file. +** +** Normal instructions are defined with: +** idef(opval, name, enum, rtn, flags) +** +** and EXTEND opcodes are defined with: +** ixdef(xopval, name, enum, rtn, flags) +** +** IO instructions (with internal or external devices) are +** defined with: +** iodef(iocod, name, enum, rtn, flags) +** where "iocod" is built with IOINOP or IOEXOP. +** +** Operations not specified here are all initialized at startup to +** an appropriate default value, usually "i_muuo" so the monitor +** can handle them. +** Only one opcode is truly illegal: 0 in EXEC mode, which currently +** stops the KLH10 rather than trapping. To change this, +** remove the definition for "ILLEG". +*/ + +idef(000, "ILLEG", I_ILLEG, i_illegal, IF_SPEC) +idef(001, "LUUO", I_LUUO, i_luuo, IF_OPN|IF_1X1) /* 001-037 inclusive */ +idef(040, "MUUO", I_MUUO, i_muuo, IF_OPN|IF_1X1) /* all others */ + +/* Opcodes 040-0101 inclusive default to i_muuo */ + + /* Late KL additions, note opcodes formerly UUOs "reserved for DEC" */ +#if KLH10_CPU_KLX + idef(052, "PMOVE", I_PMOVE, i_pmove, IF_1S) /* KL PMOVE */ + idef(053, "PMOVEM",I_PMOVEM,i_pmovem,IF_1SM) /* KL PMOVEM */ +#endif + +#if KLH10_SYS_ITS /* ITS pager XCT */ + idef(0102, "XCTRI", I_XCTRI, i_pxct, IF_SPEC|IF_MR|IF_MIN) + idef(0103, "XCTR", I_XCTR, i_pxct, IF_SPEC|IF_MR|IF_MIN) +#elif KLH10_CPU_KL + idef(0102, "GFAD", I_GFAD, i_gfad, IF_2X) /* KL GFAD */ + idef(0103, "GFSB", I_GFSB, i_gfsb, IF_2X) /* KL GFSB */ +#endif + +#if KLH10_SYS_T10 || KLH10_SYS_T20 + idef(0104, "JSYS", I_JSYS, i_muuo, IF_ME) /* BBN PAGER INSTRUCTION */ +#endif +#if KLH10_CPU_KS || KLH10_CPU_KL + idef(0105, "ADJSP", I_ADJSP, i_adjsp, IF_1XI) /* KL/KS */ +#endif +#if KLH10_CPU_KL + idef(0106, "GFMP", I_GFMP, i_gfmp, IF_2X) /* KL GFMP */ + idef(0107, "GFDV", I_GFDV, i_gfdv, IF_2X) /* KL GFDV */ +#endif +#if !KLH10_CPU_KA + idef(0110, "DFAD", I_DFAD, i_dfad, IF_2X) /* KI+ */ + idef(0111, "DFSB", I_DFSB, i_dfsb, IF_2X) /* KI+ */ + idef(0112, "DFMP", I_DFMP, i_dfmp, IF_2X) /* KI+ */ + idef(0113, "DFDV", I_DFDV, i_dfdv, IF_2X) /* KI+ */ +# if KLH10_CPU_KS || KLH10_CPU_KL + idef(0114, "DADD", I_DADD, i_dadd, IF_2X) /* KL/KS */ + idef(0115, "DSUB", I_DSUB, i_dsub, IF_2X) /* KL/KS */ + idef(0116, "DMUL", I_DMUL, i_dmul, IF_AS|IF_A4|IF_MR|IF_M2) /* KL/KS */ + idef(0117, "DDIV", I_DDIV, i_ddiv, IF_AS|IF_A4|IF_MR|IF_M2) /* KL/KS */ +# endif /* KL/KS */ + idef(0120, "DMOVE", I_DMOVE, i_dmove, IF_2S) /* KI+ */ + idef(0121, "DMOVN", I_DMOVN, i_dmovn, IF_2S) /* KI+ */ + idef(0122, "FIX", I_FIX, i_fix, IF_1S) /* KI+ */ +# if KLH10_CPU_KS || KLH10_CPU_KL + idef(0123,"EXTEND",I_EXTEND,i_extend,IF_SPEC|IF_AS|IF_A4|IF_MR|IF_M1) /* KL/KS */ +# endif /* KL/KS */ + idef(0124, "DMOVEM",I_DMOVEM,i_dmovem, IF_2SM) /* KI+ */ + idef(0125, "DMOVNM",I_DMOVNM,i_dmovnm, IF_2SM) /* KI+ */ + idef(0126, "FIXR", I_FIXR, i_fixr, IF_1S) /* KI+ */ + idef(0127, "FLTR", I_FLTR, i_fltr, IF_1S) /* KI+ */ +#endif /* !KA */ +idef(0130, "UFA", I_UFA, i_ufa, IF_AS|IF_A2|IF_MR|IF_M1) /* KA/KI */ +idef(0131, "DFN", I_DFN, i_dfn, IF_1XB) /* KA/KI only */ +idef(0132, "FSC", I_FSC, i_fsc, IF_1XI) +idef(0133, "IBP", I_IBP, i_ibp, IF_AS|IF_A0|IF_MS|IF_M1) +idef(0134, "ILDB", I_ILDB, i_ildb, IF_AW|IF_A1|IF_MS|IF_M1) +idef(0135, "LDB", I_LDB, i_ldb, IF_AW|IF_A1|IF_MR|IF_M1) +idef(0136, "IDPB", I_IDPB, i_idpb, IF_AR|IF_A1|IF_MS|IF_M1) +idef(0137, "DPB", I_DPB, i_dpb, IF_AR|IF_A1|IF_MS|IF_M1) +idef(0140, "FAD", I_FAD, i_fad, IF_1X) +idef(0141, "FADL", I_FADL, i_fadl, IF_1XFL) /* PDP6/KA/KI (not KL/KS) */ +idef(0142, "FADM", I_FADM, i_fadm, IF_1XM) +idef(0143, "FADB", I_FADB, i_fadb, IF_1XB) +idef(0144, "FADR", I_FADR, i_fadr, IF_1X) +idef(0145, "FADRI", I_FADRI, i_fadri, IF_1XFI) /* KA+ INSTR (PDP6: FADRL) */ +idef(0146, "FADRM", I_FADRM, i_fadrm, IF_1XM) +idef(0147, "FADRB", I_FADRB, i_fadrb, IF_1XB) +idef(0150, "FSB", I_FSB, i_fsb, IF_1X) +idef(0151, "FSBL", I_FSBL, i_fsbl, IF_1XFL) /* PDP6/KA/KI (not KL/KS) */ +idef(0152, "FSBM", I_FSBM, i_fsbm, IF_1XM) +idef(0153, "FSBB", I_FSBB, i_fsbb, IF_1XB) +idef(0154, "FSBR", I_FSBR, i_fsbr, IF_1X) +idef(0155, "FSBRI", I_FSBRI, i_fsbri, IF_1XFI) /* KA+ INSTR (PDP6: FSBRL) */ +idef(0156, "FSBRM", I_FSBRM, i_fsbrm, IF_1XM) +idef(0157, "FSBRB", I_FSBRB, i_fsbrb, IF_1XB) +idef(0160, "FMP", I_FMP, i_fmp, IF_1X) +idef(0161, "FMPL", I_FMPL, i_fmpl, IF_1XFL) /* PDP6/KA/KI (not KL/KS) */ +idef(0162, "FMPM", I_FMPM, i_fmpm, IF_1XM) +idef(0163, "FMPB", I_FMPB, i_fmpb, IF_1XB) +idef(0164, "FMPR", I_FMPR, i_fmpr, IF_1X) +idef(0165, "FMPRI", I_FMPRI, i_fmpri, IF_1XFI) /* KA+ INSTR (PDP6: FMPRL) */ +idef(0166, "FMPRM", I_FMPRM, i_fmprm, IF_1XM) +idef(0167, "FMPRB", I_FMPRB, i_fmprb, IF_1XB) +idef(0170, "FDV", I_FDV, i_fdv, IF_1X) +idef(0171, "FDVL", I_FDVL, i_fdvl, IF_1XFL) /* PDP6/KA/KI (not KL/KS) */ +idef(0172, "FDVM", I_FDVM, i_fdvm, IF_1XM) +idef(0173, "FDVB", I_FDVB, i_fdvb, IF_1XB) +idef(0174, "FDVR", I_FDVR, i_fdvr, IF_1X) +idef(0175, "FDVRI", I_FDVRI, i_fdvri, IF_1XFI) /* KA+ INSTR (PDP6: FDVRL) */ +idef(0176, "FDVRM", I_FDVRM, i_fdvrm, IF_1XM) +idef(0177, "FDVRB", I_FDVRB, i_fdvrb, IF_1XB) + + /* ; 200-277 (MOVE - SUBB) */ +idef(0200, "MOVE", I_MOVE, i_move, IF_1S) +idef(0201, "MOVEI", I_MOVEI, i_movei, IF_1SI) +idef(0202, "MOVEM", I_MOVEM, i_movem, IF_1SM) +idef(0203, "MOVES", I_MOVES, i_moves, IF_1SS) +idef(0204, "MOVS", I_MOVS, i_movs, IF_1S) +idef(0205, "MOVSI", I_MOVSI, i_movsi, IF_1SI) +idef(0206, "MOVSM", I_MOVSM, i_movsm, IF_1SM) +idef(0207, "MOVSS", I_MOVSS, i_movss, IF_1SS) +idef(0210, "MOVN", I_MOVN, i_movn, IF_1S) +idef(0211, "MOVNI", I_MOVNI, i_movni, IF_1SI) +idef(0212, "MOVNM", I_MOVNM, i_movnm, IF_1SM) +idef(0213, "MOVNS", I_MOVNS, i_movns, IF_1SS) +idef(0214, "MOVM", I_MOVM, i_movm, IF_1S) +idef(0215, "MOVMI", I_MOVMI, i_movei, IF_1SI) /* Same as MOVEI */ +idef(0216, "MOVMM", I_MOVMM, i_movmm, IF_1SM) +idef(0217, "MOVMS", I_MOVMS, i_movms, IF_1SS) +idef(0220, "IMUL", I_IMUL, i_imul, IF_1X) +idef(0221, "IMULI", I_IMULI, i_imuli, IF_1XI) +idef(0222, "IMULM", I_IMULM, i_imulm, IF_1XM) +idef(0223, "IMULB", I_IMULB, i_imulb, IF_1XB) +idef(0224, "MUL", I_MUL, i_mul, IF_AS|IF_A2|IF_X1) +idef(0225, "MULI", I_MULI, i_muli, IF_AS|IF_A2|IF_XI) +idef(0226, "MULM", I_MULM, i_mulm, IF_1XM) +idef(0227, "MULB", I_MULB, i_mulb, IF_AS|IF_A2|IF_MS|IF_M1) +idef(0230, "IDIV", I_IDIV, i_idiv, IF_AS|IF_A2|IF_X1) +idef(0231, "IDIVI", I_IDIVI, i_idivi, IF_AS|IF_A2|IF_XI) +idef(0232, "IDIVM", I_IDIVM, i_idivm, IF_AR|IF_A1|IF_MS|IF_M1) +idef(0233, "IDIVB", I_IDIVB, i_idivb, IF_AS|IF_A2|IF_MS|IF_M1) +idef(0234, "DIV", I_DIV, i_div, IF_AS|IF_A2|IF_X1) +idef(0235, "DIVI", I_DIVI, i_divi, IF_AS|IF_A2|IF_XI) +idef(0236, "DIVM", I_DIVM, i_divm, IF_AR|IF_A1|IF_MS|IF_M1) +idef(0237, "DIVB", I_DIVB, i_divb, IF_AS|IF_A2|IF_MS|IF_M1) +idef(0240, "ASH", I_ASH, i_ash, IF_AS|IF_A1|IF_ME8) +idef(0241, "ROT", I_ROT, i_rot, IF_AS|IF_A1|IF_ME8) +idef(0242, "LSH", I_LSH, i_lsh, IF_AS|IF_A1|IF_ME8) +idef(0243, "JFFO", I_JFFO, i_jffo, IF_AR|IF_A1|IF_ME|IF_JMP) /* KA+ */ +idef(0244, "ASHC", I_ASHC, i_ashc, IF_AR|IF_A2|IF_ME8) +idef(0245, "ROTC", I_ROTC, i_rotc, IF_AR|IF_A2|IF_ME8) +idef(0246, "LSHC", I_LSHC, i_lshc, IF_AR|IF_A2|IF_ME8) + +#if KLH10_SYS_ITS /* AI-KA and KL/KS: ROTC WITH AC+1 GOING THE WRONG WAY */ + idef(0247, "CIRC", I_CIRC, i_circ, IF_AR|IF_A2|IF_ME8) +#endif +idef(0250, "EXCH", I_EXCH, i_exch, IF_1XB) +idef(0251, "BLT", I_BLT, i_blt, IF_SPEC|IF_1XB) +idef(0252, "AOBJP", I_AOBJP, i_aobjp, IF_AS|IF_A1|IF_ME|IF_JMP) +idef(0253, "AOBJN", I_AOBJN, i_aobjn, IF_AS|IF_A1|IF_ME|IF_JMP) +idef(0254, "JRST", I_JRST, i_jrst, IF_SPEC|IF_ME|IF_JMP) +idef(0255, "JFCL", I_JFCL, i_jfcl, IF_SPEC|IF_ME|IF_JMP) +idef(0256, "XCT", I_XCT, i_xct, IF_SPEC|IF_MR|IF_MIN) +#if !KLH10_CPU_KA && (KLH10_PAG_KI || KLH10_PAG_KL) + idef(0257, "MAP", I_MAP, i_map, IF_1C|IF_X1) /* KI+ (DEC pager) */ +#endif +idef(0260, "PUSHJ", I_PUSHJ, i_pushj, IF_SPEC|IF_1C|IF_ME|IF_JMP) +idef(0261, "PUSH", I_PUSH, i_push, IF_SPEC|IF_1X) +idef(0262, "POP", I_POP, i_pop, IF_SPEC|IF_AS|IF_A1|IF_MW|IF_M1) +idef(0263, "POPJ", I_POPJ, i_popj, IF_SPEC|IF_AS|IF_A1|IF_ME|IF_JMP) +idef(0264, "JSR", I_JSR, i_jsr, IF_MW|IF_M1|IF_JMP) +idef(0265, "JSP", I_JSP, i_jsp, IF_1C|IF_ME|IF_JMP) +idef(0266, "JSA", I_JSA, i_jsa, IF_AS|IF_A1|IF_MW|IF_M1|IF_JMP) +idef(0267, "JRA", I_JRA, i_jra, IF_SPEC|IF_AS|IF_A1|IF_ME|IF_JMP) +idef(0270, "ADD", I_ADD, i_add, IF_1X) +idef(0271, "ADDI", I_ADDI, i_addi, IF_1XI) +idef(0272, "ADDM", I_ADDM, i_addm, IF_1XM) +idef(0273, "ADDB", I_ADDB, i_addb, IF_1XB) +idef(0274, "SUB", I_SUB, i_sub, IF_1X) +idef(0275, "SUBI", I_SUBI, i_subi, IF_1XI) +idef(0276, "SUBM", I_SUBM, i_subm, IF_1XM) +idef(0277, "SUBB", I_SUBB, i_subb, IF_1XB) + + /* ; 300-377 (CAI - SOSG) */ +idef(0300, "CAI", I_CAI, i_cai, IF_NOP) +idef(0301, "CAIL", I_CAIL, i_cail, IF_SKP|IF_AR|IF_A1|IF_ME) +idef(0302, "CAIE", I_CAIE, i_caie, IF_SKP|IF_AR|IF_A1|IF_ME) +idef(0303, "CAILE", I_CAILE, i_caile, IF_SKP|IF_AR|IF_A1|IF_ME) +idef(0304, "CAIA", I_CAIA, i_caia, IF_SKP) +idef(0305, "CAIGE", I_CAIGE, i_caige, IF_SKP|IF_AR|IF_A1|IF_ME) +idef(0306, "CAIN", I_CAIN, i_cain, IF_SKP|IF_AR|IF_A1|IF_ME) +idef(0307, "CAIG", I_CAIG, i_caig, IF_SKP|IF_AR|IF_A1|IF_ME) +idef(0310, "CAM", I_CAM, i_cam, IF_MR|IF_M1) +idef(0311, "CAML", I_CAML, i_caml, IF_SKP|IF_AR|IF_A1|IF_MR|IF_M1) +idef(0312, "CAME", I_CAME, i_came, IF_SKP|IF_AR|IF_A1|IF_MR|IF_M1) +idef(0313, "CAMLE", I_CAMLE, i_camle, IF_SKP|IF_AR|IF_A1|IF_MR|IF_M1) +idef(0314, "CAMA", I_CAMA, i_cama, IF_SKP|IF_MR|IF_M1) +idef(0315, "CAMGE", I_CAMGE, i_camge, IF_SKP|IF_AR|IF_A1|IF_MR|IF_M1) +idef(0316, "CAMN", I_CAMN, i_camn, IF_SKP|IF_AR|IF_A1|IF_MR|IF_M1) +idef(0317, "CAMG", I_CAMG, i_camg, IF_SKP|IF_AR|IF_A1|IF_MR|IF_M1) +idef(0320, "JUMP", I_JUMP, i_jump, IF_NOP) +idef(0321, "JUMPL", I_JUMPL, i_jumpl, IF_JMP|IF_AR|IF_A1|IF_ME) +idef(0322, "JUMPE", I_JUMPE, i_jumpe, IF_JMP|IF_AR|IF_A1|IF_ME) +idef(0323, "JUMPLE",I_JUMPLE,i_jumple, IF_JMP|IF_AR|IF_A1|IF_ME) +idef(0324, "JUMPA", I_JUMPA, i_jumpa, IF_JMP|IF_ME) +idef(0325, "JUMPGE",I_JUMPGE,i_jumpge, IF_JMP|IF_AR|IF_A1|IF_ME) +idef(0326, "JUMPN", I_JUMPN, i_jumpn, IF_JMP|IF_AR|IF_A1|IF_ME) +idef(0327, "JUMPG", I_JUMPG, i_jumpg, IF_JMP|IF_AR|IF_A1|IF_ME) +idef(0330, "SKIP", I_SKIP, i_skip, IF_AW|IF_A0|IF_MR|IF_M1) +idef(0331, "SKIPL", I_SKIPL, i_skipl, IF_SKP|IF_AW|IF_A0|IF_MR|IF_M1) +idef(0332, "SKIPE", I_SKIPE, i_skipe, IF_SKP|IF_AW|IF_A0|IF_MR|IF_M1) +idef(0333, "SKIPLE",I_SKIPLE,i_skiple, IF_SKP|IF_AW|IF_A0|IF_MR|IF_M1) +idef(0334, "SKIPA", I_SKIPA, i_skipa, IF_SKP|IF_AW|IF_A0|IF_MR|IF_M1) +idef(0335, "SKIPGE",I_SKIPGE,i_skipge, IF_SKP|IF_AW|IF_A0|IF_MR|IF_M1) +idef(0336, "SKIPN", I_SKIPN, i_skipn, IF_SKP|IF_AW|IF_A0|IF_MR|IF_M1) +idef(0337, "SKIPG", I_SKIPG, i_skipg, IF_SKP|IF_AW|IF_A0|IF_MR|IF_M1) +idef(0340, "AOJ", I_AOJ, i_aoj, IF_AS|IF_A1|IF_ME) +idef(0341, "AOJL", I_AOJL, i_aojl, IF_JMP|IF_AS|IF_A1|IF_ME) +idef(0342, "AOJE", I_AOJE, i_aoje, IF_JMP|IF_AS|IF_A1|IF_ME) +idef(0343, "AOJLE", I_AOJLE, i_aojle, IF_JMP|IF_AS|IF_A1|IF_ME) +idef(0344, "AOJA", I_AOJA, i_aoja, IF_JMP|IF_AS|IF_A1|IF_ME) +idef(0345, "AOJGE", I_AOJGE, i_aojge, IF_JMP|IF_AS|IF_A1|IF_ME) +idef(0346, "AOJN", I_AOJN, i_aojn, IF_JMP|IF_AS|IF_A1|IF_ME) +idef(0347, "AOJG", I_AOJG, i_aojg, IF_JMP|IF_AS|IF_A1|IF_ME) +idef(0350, "AOS", I_AOS, i_aos, IF_AW|IF_A0|IF_MS|IF_M1) +idef(0351, "AOSL", I_AOSL, i_aosl, IF_SKP|IF_AW|IF_A0|IF_MS|IF_M1) +idef(0352, "AOSE", I_AOSE, i_aose, IF_SKP|IF_AW|IF_A0|IF_MS|IF_M1) +idef(0353, "AOSLE", I_AOSLE, i_aosle, IF_SKP|IF_AW|IF_A0|IF_MS|IF_M1) +idef(0354, "AOSA", I_AOSA, i_aosa, IF_SKP|IF_AW|IF_A0|IF_MS|IF_M1) +idef(0355, "AOSGE", I_AOSGE, i_aosge, IF_SKP|IF_AW|IF_A0|IF_MS|IF_M1) +idef(0356, "AOSN", I_AOSN, i_aosn, IF_SKP|IF_AW|IF_A0|IF_MS|IF_M1) +idef(0357, "AOSG", I_AOSG, i_aosg, IF_SKP|IF_AW|IF_A0|IF_MS|IF_M1) +idef(0360, "SOJ", I_SOJ, i_soj, IF_AS|IF_A1|IF_ME) +idef(0361, "SOJL", I_SOJL, i_sojl, IF_JMP|IF_AS|IF_A1|IF_ME) +idef(0362, "SOJE", I_SOJE, i_soje, IF_JMP|IF_AS|IF_A1|IF_ME) +idef(0363, "SOJLE", I_SOJLE, i_sojle, IF_JMP|IF_AS|IF_A1|IF_ME) +idef(0364, "SOJA", I_SOJA, i_soja, IF_JMP|IF_AS|IF_A1|IF_ME) +idef(0365, "SOJGE", I_SOJGE, i_sojge, IF_JMP|IF_AS|IF_A1|IF_ME) +idef(0366, "SOJN", I_SOJN, i_sojn, IF_JMP|IF_AS|IF_A1|IF_ME) +idef(0367, "SOJG", I_SOJG, i_sojg, IF_JMP|IF_AS|IF_A1|IF_ME) +idef(0370, "SOS", I_SOS, i_sos, IF_AW|IF_A0|IF_MS|IF_M1) +idef(0371, "SOSL", I_SOSL, i_sosl, IF_SKP|IF_AW|IF_A0|IF_MS|IF_M1) +idef(0372, "SOSE", I_SOSE, i_sose, IF_SKP|IF_AW|IF_A0|IF_MS|IF_M1) +idef(0373, "SOSLE", I_SOSLE, i_sosle, IF_SKP|IF_AW|IF_A0|IF_MS|IF_M1) +idef(0374, "SOSA", I_SOSA, i_sosa, IF_SKP|IF_AW|IF_A0|IF_MS|IF_M1) +idef(0375, "SOSGE", I_SOSGE, i_sosge, IF_SKP|IF_AW|IF_A0|IF_MS|IF_M1) +idef(0376, "SOSN", I_SOSN, i_sosn, IF_SKP|IF_AW|IF_A0|IF_MS|IF_M1) +idef(0377, "SOSG", I_SOSG, i_sosg, IF_SKP|IF_AW|IF_A0|IF_MS|IF_M1) + + /* ; 400-477 (SETZ - SETOB) */ +idef(0400, "SETZ", I_SETZ, i_setz, IF_1C) +idef(0401, "SETZI", I_SETZI, i_setzi, IF_1C) +idef(0402, "SETZM", I_SETZM, i_setzm, IF_1CM) +idef(0403, "SETZB", I_SETZB, i_setzb, IF_1CB) +idef(0404, "AND", I_AND, i_and, IF_1X) +idef(0405, "ANDI", I_ANDI, i_andi, IF_1XI) +idef(0406, "ANDM", I_ANDM, i_andm, IF_1XM) +idef(0407, "ANDB", I_ANDB, i_andb, IF_1XB) +idef(0410, "ANDCA", I_ANDCA, i_andca, IF_1X) +idef(0411, "ANDCAI",I_ANDCAI,i_andcai, IF_1XI) +idef(0412, "ANDCAM",I_ANDCAM,i_andcam, IF_1XM) +idef(0413, "ANDCAB",I_ANDCAB,i_andcab, IF_1XB) +idef(0414, "SETM", I_SETM, i_setm, IF_1S) +#if KLH10_EXTADR + idef(0415,"XMOVEI",I_XMOVEI,i_xmovei, IF_1SI) /* Extended version */ +#else + idef(0415,"SETMI", I_SETMI, i_setmi, IF_1SI) +#endif +idef(0416, "SETMM", I_SETMM, i_setmm, IF_MS|IF_M1) +idef(0417, "SETMB", I_SETMB, i_setmb, IF_1SB) +idef(0420, "ANDCM", I_ANDCM, i_andcm, IF_1X) +idef(0421, "ANDCMI",I_ANDCMI,i_andcmi, IF_1XI) +idef(0422, "ANDCMM",I_ANDCMM,i_andcmm, IF_1XM) +idef(0423, "ANDCMB",I_ANDCMB,i_andcmb, IF_1XB) +idef(0424, "SETA", I_SETA, i_seta, IF_NOP) +idef(0425, "SETAI", I_SETAI, i_setai, IF_NOP) +idef(0426, "SETAM", I_SETAM, i_setam, IF_1SM) +idef(0427, "SETAB", I_SETAB, i_setab, IF_1SM) +idef(0430, "XOR", I_XOR, i_xor, IF_1X) +idef(0431, "XORI", I_XORI, i_xori, IF_1XI) +idef(0432, "XORM", I_XORM, i_xorm, IF_1XM) +idef(0433, "XORB", I_XORB, i_xorb, IF_1XB) +idef(0434, "IOR", I_IOR, i_ior, IF_1X) +idef(0435, "IORI", I_IORI, i_iori, IF_1XI) +idef(0436, "IORM", I_IORM, i_iorm, IF_1XM) +idef(0437, "IORB", I_IORB, i_iorb, IF_1XB) +idef(0440, "ANDCB", I_ANDCB, i_andcb, IF_1X) +idef(0441, "ANDCBI",I_ANDCBI,i_andcbi, IF_1XI) +idef(0442, "ANDCBM",I_ANDCBM,i_andcbm, IF_1XM) +idef(0443, "ANDCBB",I_ANDCBB,i_andcbb, IF_1XB) +idef(0444, "EQV", I_EQV, i_eqv, IF_1X) +idef(0445, "EQVI", I_EQVI, i_eqvi, IF_1XI) +idef(0446, "EQVM", I_EQVM, i_eqvm, IF_1XM) +idef(0447, "EQVB", I_EQVB, i_eqvb, IF_1XB) +idef(0450, "SETCA", I_SETCA, i_setca, IF_AS|IF_A1) +idef(0451, "SETCAI",I_SETCAI,i_setcai, IF_AS|IF_A1) +idef(0452, "SETCAM",I_SETCAM,i_setcam, IF_1SM) +idef(0453, "SETCAB",I_SETCAB,i_setcab, IF_AS|IF_A1|IF_MW|IF_M1) +idef(0454, "ORCA", I_ORCA, i_orca, IF_1X) +idef(0455, "ORCAI", I_ORCAI, i_orcai, IF_1XI) +idef(0456, "ORCAM", I_ORCAM, i_orcam, IF_1XM) +idef(0457, "ORCAB", I_ORCAB, i_orcab, IF_1XB) +idef(0460, "SETCM", I_SETCM, i_setcm, IF_1X) +idef(0461, "SETCMI",I_SETCMI,i_setcmi, IF_1XI) +idef(0462, "SETCMM",I_SETCMM,i_setcmm, IF_1XM) +idef(0463, "SETCMB",I_SETCMB,i_setcmb, IF_1XB) +idef(0464, "ORCM", I_ORCM, i_orcm, IF_1X) +idef(0465, "ORCMI", I_ORCMI, i_orcmi, IF_1XI) +idef(0466, "ORCMM", I_ORCMM, i_orcmm, IF_1XM) +idef(0467, "ORCMB", I_ORCMB, i_orcmb, IF_1XB) +idef(0470, "ORCB", I_ORCB, i_orcb, IF_1X) +idef(0471, "ORCBI", I_ORCBI, i_orcbi, IF_1XI) +idef(0472, "ORCBM", I_ORCBM, i_orcbm, IF_1XM) +idef(0473, "ORCBB", I_ORCBB, i_orcbb, IF_1XB) +idef(0474, "SETO", I_SETO, i_seto, IF_1C) +idef(0475, "SETOI", I_SETOI, i_setoi, IF_1C) +idef(0476, "SETOM", I_SETOM, i_setom, IF_1CM) +idef(0477, "SETOB", I_SETOB, i_setob, IF_1CB) + + /* ; 500-577 (HLL - HLRES) */ +idef(0500, "HLL", I_HLL, i_hll, IF_1S) +#if KLH10_EXTADR + idef(0501,"XHLLI", I_XHLLI, i_xhlli, IF_1C) /* Extended version */ +#else + idef(0501,"HLLI", I_HLLI, i_hlli, IF_1C) +#endif +idef(0502, "HLLM", I_HLLM, i_hllm, IF_1SM) +idef(0503, "HLLS", I_HLLS, i_hlls, IF_1SS) +idef(0504, "HRL", I_HRL, i_hrl, IF_1S) +idef(0505, "HRLI", I_HRLI, i_hrli, IF_1SI) +idef(0506, "HRLM", I_HRLM, i_hrlm, IF_1SM) +idef(0507, "HRLS", I_HRLS, i_hrls, IF_1SS) +idef(0510, "HLLZ", I_HLLZ, i_hllz, IF_1S) +idef(0511, "HLLZI", I_HLLZI, i_hllzi, IF_1C) +idef(0512, "HLLZM", I_HLLZM, i_hllzm, IF_1SM) +idef(0513, "HLLZS", I_HLLZS, i_hllzs, IF_1SS) +idef(0514, "HRLZ", I_HRLZ, i_hrlz, IF_1S) +idef(0515, "HRLZI", I_HRLZI, i_hrlzi, IF_1C) +idef(0516, "HRLZM", I_HRLZM, i_hrlzm, IF_1SM) +idef(0517, "HRLZS", I_HRLZS, i_hrlzs, IF_1SS) +idef(0520, "HLLO", I_HLLO, i_hllo, IF_1S) +idef(0521, "HLLOI", I_HLLOI, i_hlloi, IF_1C) +idef(0522, "HLLOM", I_HLLOM, i_hllom, IF_1SM) +idef(0523, "HLLOS", I_HLLOS, i_hllos, IF_1SS) +idef(0524, "HRLO", I_HRLO, i_hrlo, IF_1S) +idef(0525, "HRLOI", I_HRLOI, i_hrloi, IF_1C) +idef(0526, "HRLOM", I_HRLOM, i_hrlom, IF_1SM) +idef(0527, "HRLOS", I_HRLOS, i_hrlos, IF_1SS) +idef(0530, "HLLE", I_HLLE, i_hlle, IF_1S) +idef(0531, "HLLEI", I_HLLEI, i_hllei, IF_1C) +idef(0532, "HLLEM", I_HLLEM, i_hllem, IF_1SM) +idef(0533, "HLLES", I_HLLES, i_hlles, IF_1SS) +idef(0534, "HRLE", I_HRLE, i_hrle, IF_1S) +idef(0535, "HRLEI", I_HRLEI, i_hrlei, IF_1SI) +idef(0536, "HRLEM", I_HRLEM, i_hrlem, IF_1SM) +idef(0537, "HRLES", I_HRLES, i_hrles, IF_1SS) +idef(0540, "HRR", I_HRR, i_hrr, IF_1S) +idef(0541, "HRRI", I_HRRI, i_hrri, IF_1SI) +idef(0542, "HRRM", I_HRRM, i_hrrm, IF_1SM) +idef(0543, "HRRS", I_HRRS, i_hrrs, IF_1SS) +idef(0544, "HLR", I_HLR, i_hlr, IF_1S) +idef(0545, "HLRI", I_HLRI, i_hlri, IF_1C) +idef(0546, "HLRM", I_HLRM, i_hlrm, IF_1SM) +idef(0547, "HLRS", I_HLRS, i_hlrs, IF_1SS) +idef(0550, "HRRZ", I_HRRZ, i_hrrz, IF_1S) +idef(0551, "HRRZI", I_HRRZI, i_hrrzi, IF_1SI) +idef(0552, "HRRZM", I_HRRZM, i_hrrzm, IF_1SM) +idef(0553, "HRRZS", I_HRRZS, i_hrrzs, IF_1SS) +idef(0554, "HLRZ", I_HLRZ, i_hlrz, IF_1S) +idef(0555, "HLRZI", I_HLRZI, i_hlrzi, IF_1C) +idef(0556, "HLRZM", I_HLRZM, i_hlrzm, IF_1SM) +idef(0557, "HLRZS", I_HLRZS, i_hlrzs, IF_1SS) +idef(0560, "HRRO", I_HRRO, i_hrro, IF_1S) +idef(0561, "HRROI", I_HRROI, i_hrroi, IF_1SI) +idef(0562, "HRROM", I_HRROM, i_hrrom, IF_1SM) +idef(0563, "HRROS", I_HRROS, i_hrros, IF_1SS) +idef(0564, "HLRO", I_HLRO, i_hlro, IF_1S) +idef(0565, "HLROI", I_HLROI, i_hlroi, IF_1C) +idef(0566, "HLROM", I_HLROM, i_hlrom, IF_1SM) +idef(0567, "HLROS", I_HLROS, i_hlros, IF_1SS) +idef(0570, "HRRE", I_HRRE, i_hrre, IF_1S) +idef(0571, "HRREI", I_HRREI, i_hrrei, IF_1SI) +idef(0572, "HRREM", I_HRREM, i_hrrem, IF_1SM) +idef(0573, "HRRES", I_HRRES, i_hrres, IF_1SS) +idef(0574, "HLRE", I_HLRE, i_hlre, IF_1S) +idef(0575, "HLREI", I_HLREI, i_hlrei, IF_1C) +idef(0576, "HLREM", I_HLREM, i_hlrem, IF_1SM) +idef(0577, "HLRES", I_HLRES, i_hlres, IF_1SS) + + /* ; 600-677 (TRN - TSON) */ +idef(0600, "TRN", I_TRN, i_trn, IF_NOP) +idef(0601, "TLN", I_TLN, i_tln, IF_NOP) +idef(0602, "TRNE", I_TRNE, i_trne, IF_SKP|IF_AR|IF_A1|IF_ME) +idef(0603, "TLNE", I_TLNE, i_tlne, IF_SKP|IF_AR|IF_A1|IF_ME) +idef(0604, "TRNA", I_TRNA, i_trna, IF_SKP) +idef(0605, "TLNA", I_TLNA, i_tlna, IF_SKP) +idef(0606, "TRNN", I_TRNN, i_trnn, IF_SKP|IF_AR|IF_A1|IF_ME) +idef(0607, "TLNN", I_TLNN, i_tlnn, IF_SKP|IF_AR|IF_A1|IF_ME) +idef(0610, "TDN", I_TDN, i_tdn, IF_MR|IF_M1) +idef(0611, "TSN", I_TSN, i_tsn, IF_MR|IF_M1) +idef(0612, "TDNE", I_TDNE, i_tdne, IF_SKP|IF_AR|IF_A1|IF_MR|IF_M1) +idef(0613, "TSNE", I_TSNE, i_tsne, IF_SKP|IF_AR|IF_A1|IF_MR|IF_M1) +idef(0614, "TDNA", I_TDNA, i_tdna, IF_SKP|IF_AR|IF_A1|IF_MR|IF_M1) +idef(0615, "TSNA", I_TSNA, i_tsna, IF_SKP|IF_AR|IF_A1|IF_MR|IF_M1) +idef(0616, "TDNN", I_TDNN, i_tdnn, IF_SKP|IF_AR|IF_A1|IF_MR|IF_M1) +idef(0617, "TSNN", I_TSNN, i_tsnn, IF_SKP|IF_AR|IF_A1|IF_MR|IF_M1) +idef(0620, "TRZ", I_TRZ, i_trz, IF_AS|IF_A1|IF_ME) +idef(0621, "TLZ", I_TLZ, i_tlz, IF_AS|IF_A1|IF_ME) +idef(0622, "TRZE", I_TRZE, i_trze, IF_SKP|IF_AS|IF_A1|IF_ME) +idef(0623, "TLZE", I_TLZE, i_tlze, IF_SKP|IF_AS|IF_A1|IF_ME) +idef(0624, "TRZA", I_TRZA, i_trza, IF_SKP|IF_AS|IF_A1|IF_ME) +idef(0625, "TLZA", I_TLZA, i_tlza, IF_SKP|IF_AS|IF_A1|IF_ME) +idef(0626, "TRZN", I_TRZN, i_trzn, IF_SKP|IF_AS|IF_A1|IF_ME) +idef(0627, "TLZN", I_TLZN, i_tlzn, IF_SKP|IF_AS|IF_A1|IF_ME) +idef(0630, "TDZ", I_TDZ, i_tdz, IF_AS|IF_A1|IF_MR|IF_M1) +idef(0631, "TSZ", I_TSZ, i_tsz, IF_AS|IF_A1|IF_MR|IF_M1) +idef(0632, "TDZE", I_TDZE, i_tdze, IF_SKP|IF_AS|IF_A1|IF_MR|IF_M1) +idef(0633, "TSZE", I_TSZE, i_tsze, IF_SKP|IF_AS|IF_A1|IF_MR|IF_M1) +idef(0634, "TDZA", I_TDZA, i_tdza, IF_SKP|IF_AS|IF_A1|IF_MR|IF_M1) +idef(0635, "TSZA", I_TSZA, i_tsza, IF_SKP|IF_AS|IF_A1|IF_MR|IF_M1) +idef(0636, "TDZN", I_TDZN, i_tdzn, IF_SKP|IF_AS|IF_A1|IF_MR|IF_M1) +idef(0637, "TSZN", I_TSZN, i_tszn, IF_SKP|IF_AS|IF_A1|IF_MR|IF_M1) +idef(0640, "TRC", I_TRC, i_trc, IF_AS|IF_A1|IF_ME) +idef(0641, "TLC", I_TLC, i_tlc, IF_AS|IF_A1|IF_ME) +idef(0642, "TRCE", I_TRCE, i_trce, IF_SKP|IF_AS|IF_A1|IF_ME) +idef(0643, "TLCE", I_TLCE, i_tlce, IF_SKP|IF_AS|IF_A1|IF_ME) +idef(0644, "TRCA", I_TRCA, i_trca, IF_SKP|IF_AS|IF_A1|IF_ME) +idef(0645, "TLCA", I_TLCA, i_tlca, IF_SKP|IF_AS|IF_A1|IF_ME) +idef(0646, "TRCN", I_TRCN, i_trcn, IF_SKP|IF_AS|IF_A1|IF_ME) +idef(0647, "TLCN", I_TLCN, i_tlcn, IF_SKP|IF_AS|IF_A1|IF_ME) +idef(0650, "TDC", I_TDC, i_tdc, IF_AS|IF_A1|IF_MR|IF_M1) +idef(0651, "TSC", I_TSC, i_tsc, IF_AS|IF_A1|IF_MR|IF_M1) +idef(0652, "TDCE", I_TDCE, i_tdce, IF_SKP|IF_AS|IF_A1|IF_MR|IF_M1) +idef(0653, "TSCE", I_TSCE, i_tsce, IF_SKP|IF_AS|IF_A1|IF_MR|IF_M1) +idef(0654, "TDCA", I_TDCA, i_tdca, IF_SKP|IF_AS|IF_A1|IF_MR|IF_M1) +idef(0655, "TSCA", I_TSCA, i_tsca, IF_SKP|IF_AS|IF_A1|IF_MR|IF_M1) +idef(0656, "TDCN", I_TDCN, i_tdcn, IF_SKP|IF_AS|IF_A1|IF_MR|IF_M1) +idef(0657, "TSCN", I_TSCN, i_tscn, IF_SKP|IF_AS|IF_A1|IF_MR|IF_M1) +idef(0660, "TRO", I_TRO, i_tro, IF_AS|IF_A1|IF_ME) +idef(0661, "TLO", I_TLO, i_tlo, IF_AS|IF_A1|IF_ME) +idef(0662, "TROE", I_TROE, i_troe, IF_SKP|IF_AS|IF_A1|IF_ME) +idef(0663, "TLOE", I_TLOE, i_tloe, IF_SKP|IF_AS|IF_A1|IF_ME) +idef(0664, "TROA", I_TROA, i_troa, IF_SKP|IF_AS|IF_A1|IF_ME) +idef(0665, "TLOA", I_TLOA, i_tloa, IF_SKP|IF_AS|IF_A1|IF_ME) +idef(0666, "TRON", I_TRON, i_tron, IF_SKP|IF_AS|IF_A1|IF_ME) +idef(0667, "TLON", I_TLON, i_tlon, IF_SKP|IF_AS|IF_A1|IF_ME) +idef(0670, "TDO", I_TDO, i_tdo, IF_AS|IF_A1|IF_MR|IF_M1) +idef(0671, "TSO", I_TSO, i_tso, IF_AS|IF_A1|IF_MR|IF_M1) +idef(0672, "TDOE", I_TDOE, i_tdoe, IF_SKP|IF_AS|IF_A1|IF_MR|IF_M1) +idef(0673, "TSOE", I_TSOE, i_tsoe, IF_SKP|IF_AS|IF_A1|IF_MR|IF_M1) +idef(0674, "TDOA", I_TDOA, i_tdoa, IF_SKP|IF_AS|IF_A1|IF_MR|IF_M1) +idef(0675, "TSOA", I_TSOA, i_tsoa, IF_SKP|IF_AS|IF_A1|IF_MR|IF_M1) +idef(0676, "TDON", I_TDON, i_tdon, IF_SKP|IF_AS|IF_A1|IF_MR|IF_M1) +idef(0677, "TSON", I_TSON, i_tson, IF_SKP|IF_AS|IF_A1|IF_MR|IF_M1) + +/* I/O INSTRUCTION DISPATCH */ + +/* Opcodes 0700-0777 inclusive default to either i_muuo or i_diodisp */ + +#if KLH10_CPU_KS || KLH10_CPU_KL /* "Internal" devices; AC-dispatched */ + idef(0700, "IO700", I_IOV0, i_io700disp, IF_IO) + idef(0701, "IO701", I_IOV1, i_io701disp, IF_IO) + idef(0702, "IO702", I_IOV2, i_io702disp, IF_IO) +#endif /* KS || KL */ + +/* idef(0703, NULL, I_IO03, i_703, IF_IO) */ + +#if KLH10_CPU_KS /* Only KS, sigh... */ + idef(0704, "UMOVE", I_UMOVE, i_umove, IF_SPEC) /* KS = PXCT 4,[MOVE] */ + idef(0705, "UMOVEM", I_UMOVEM, i_umovem, IF_SPEC) /* KS = PXCT 4,[MOVEM] */ +#endif /* KS */ + +/* idef(0706, NULL, I_IO06, i_706, IF_IO) */ +/* idef(0707, NULL, I_IO07, i_707, IF_IO) */ + +#if KLH10_CPU_KS +# if KLH10_SYS_ITS + idef(0710, "IORDI", I_IORDI, i_iordi, IF_MEIO) + idef(0711, "IORDQ", I_IORDQ, i_iordq, IF_MEIO) + idef(0712, "IORD", I_IORD, i_iord, IF_MEIO) + idef(0713, "IOWR", I_IOWR, i_iowr, IF_MEIO) + idef(0714, "IOWRI", I_IOWRI, i_iowri, IF_MEIO) + idef(0715, "IOWRQ", I_IOWRQ, i_iowrq, IF_MEIO) +# else /* DEC */ + idef(0710, "TIOE", I_TIOE, i_tioe, IF_MEIO) + idef(0711, "TION", I_TION, i_tion, IF_MEIO) + idef(0712, "RDIO", I_RDIO, i_rdio, IF_MEIO) + idef(0713, "WRIO", I_WRIO, i_wrio, IF_MEIO) + idef(0714, "BSIO", I_BSIO, i_bsio, IF_MEIO) + idef(0715, "BCIO", I_BCIO, i_bcio, IF_MEIO) +# endif /* DEC */ + + idef(0716, "BLTBU", I_BLTBU, i_bltbu, IF_SPEC|IF_AS|IF_A1|IF_MW|IF_M1) + idef(0717, "BLTUB", I_BLTUB, i_bltub, IF_SPEC|IF_AS|IF_A1|IF_MW|IF_M1) + +# if KLH10_SYS_ITS + idef(0720, "IORDBI", I_IORDBI, i_iordbi, IF_MEIO) + idef(0721, "IORDBQ", I_IORDBQ, i_iordbq, IF_MEIO) + idef(0722, "IORDB", I_IORDB, i_iordb, IF_MEIO) + idef(0723, "IOWRB", I_IOWRB, i_iowrb, IF_MEIO) + idef(0724, "IOWRBI", I_IOWRBI, i_iowrbi, IF_MEIO) + idef(0725, "IOWRBQ", I_IOWRBQ, i_iowrbq, IF_MEIO) +# else /* DEC */ + idef(0720, "TIOEB", I_TIOEB, i_tioeb, IF_MEIO) + idef(0721, "TIONB", I_TIONB, i_tionb, IF_MEIO) + idef(0722, "RDIOB", I_RDIOB, i_rdiob, IF_MEIO) + idef(0723, "WRIOB", I_WRIOB, i_wriob, IF_MEIO) + idef(0724, "BSIOB", I_BSIOB, i_bsiob, IF_MEIO) + idef(0725, "BCIOB", I_BCIOB, i_bciob, IF_MEIO) +# endif /* DEC */ +#endif /* KLH10_CPU_KS */ + +/* +idef(0726, NULL, I_IO26, i_726, IF_IO) +idef(0727, NULL, I_IO27, i_727, IF_IO) +idef(0730, NULL, I_IO30, i_730, IF_IO) +idef(0731, NULL, I_IO31, i_731, IF_IO) +idef(0732, NULL, I_IO32, i_732, IF_IO) +idef(0733, NULL, I_IO33, i_733, IF_IO) +idef(0734, NULL, I_IO34, i_734, IF_IO) +idef(0735, NULL, I_IO35, i_735, IF_IO) +idef(0736, NULL, I_IO36, i_736, IF_IO) +idef(0737, NULL, I_IO37, i_737, IF_IO) +idef(0740, NULL, I_IO40, i_740, IF_IO) +idef(0741, NULL, I_IO41, i_741, IF_IO) +idef(0742, NULL, I_IO42, i_742, IF_IO) +idef(0743, NULL, I_IO43, i_743, IF_IO) +idef(0744, NULL, I_IO44, i_744, IF_IO) +idef(0745, NULL, I_IO45, i_745, IF_IO) +idef(0746, NULL, I_IO46, i_746, IF_IO) +idef(0747, NULL, I_IO47, i_747, IF_IO) +idef(0750, NULL, I_IO50, i_750, IF_IO) +idef(0751, NULL, I_IO51, i_751, IF_IO) +idef(0752, NULL, I_IO52, i_752, IF_IO) +idef(0753, NULL, I_IO53, i_753, IF_IO) +idef(0754, NULL, I_IO54, i_754, IF_IO) +idef(0755, NULL, I_IO55, i_755, IF_IO) +idef(0756, NULL, I_IO56, i_756, IF_IO) +idef(0757, NULL, I_IO57, i_757, IF_IO) +idef(0760, NULL, I_IO60, i_760, IF_IO) +idef(0761, NULL, I_IO61, i_761, IF_IO) +idef(0762, NULL, I_IO62, i_762, IF_IO) +idef(0763, NULL, I_IO63, i_763, IF_IO) +idef(0764, NULL, I_IO64, i_764, IF_IO) +idef(0765, NULL, I_IO65, i_765, IF_IO) +idef(0766, NULL, I_IO66, i_766, IF_IO) +idef(0767, NULL, I_IO67, i_767, IF_IO) +idef(0770, NULL, I_IO70, i_770, IF_IO) +idef(0771, NULL, I_IO71, i_771, IF_IO) +idef(0772, NULL, I_IO72, i_772, IF_IO) +idef(0773, NULL, I_IO73, i_773, IF_IO) +idef(0774, NULL, I_IO74, i_774, IF_IO) +idef(0775, NULL, I_IO75, i_775, IF_IO) +idef(0776, NULL, I_IO76, i_776, IF_IO) +idef(0777, NULL, I_IO77, i_777, IF_IO) +*/ + +/* "INTERNAL" DEVICE IO INSTRUCTIONS (0700-0702 inclusive) */ + + /* APR */ +iodef(IOINOP(0700, 0), "APRID", IO_APRID, io_aprid, IF_IO) /* BI APR, */ +#if KLH10_CPU_KL + /* DATAI APR, - (KA/KI: Read console switches) */ + iodef(IOINOP(0700, 01), NULL, IO_DI_APR, io_di_apr, IF_IO) /* DI APR, */ + iodef(IOINOP(0700, 02), "WRFIL", IO_WRFIL, io_wrfil, IF_IO) /* BO APR, */ + /* DATAO APR, - (KA: set relocs) (KI: set maint) */ + iodef(IOINOP(0700, 03), NULL, IO_DO_APR, io_do_apr, IF_IO) /* DO APR, */ +#endif +iodef(IOINOP(0700, 04), "WRAPR", IO_WRAPR, io_wrapr, IF_IO) /* CO APR, */ +iodef(IOINOP(0700, 05), "RDAPR", IO_RDAPR, io_rdapr, IF_IO) /* CI APR, */ +iodef(IOINOP(0700, 06), NULL, IO_SZ_APR, io_sz_apr, IF_IO) /* SZ APR, */ +iodef(IOINOP(0700, 07), NULL, IO_SO_APR, io_so_apr, IF_IO) /* SO APR, */ + + /* PI */ +#if KLH10_CPU_KL + iodef(IOINOP(0700, 010), "RDERA", IO_RDERA, io_rdera, IF_IO) /* BI PI, */ +#endif +#if 0 + iodef(IOINOP(0700, 011), NULL, IO_DI_PI, NULL, IF_IO) /* DI PI, */ +#endif +#if KLH10_CPU_KL + iodef(IOINOP(0700, 012),"SBDIAG",IO_SBDIAG,io_sbdiag,IF_IO) /* BO PI, */ +#endif +#if 0 /* DATAO PI, - (KA/KI: Disp data on console lites) */ + iodef(IOINOP(0700, 013), NULL, IO_DO_PI, NULL, IF_IO) /* DO PI, */ +#endif +iodef(IOINOP(0700, 014), "WRPI", IO_WRPI, io_wrpi, IF_IO) /* CO PI, */ +iodef(IOINOP(0700, 015), "RDPI", IO_RDPI, io_rdpi, IF_IO) /* CI PI, */ +iodef(IOINOP(0700, 016), NULL, IO_SZ_PI, io_sz_pi, IF_IO) /* SZ PI, */ +iodef(IOINOP(0700, 017), NULL, IO_SO_PI, io_so_pi, IF_IO) /* SO PI, */ + + /* PAG */ +#if KLH10_CPU_KS || KLH10_CPU_KL +# if KLH10_SYS_ITS + iodef(IOINOP(0701, 0),"CLRCSH",IO_CLRCSH,io_clrcsh,IF_IO) /* BI PAG, */ +# endif + iodef(IOINOP(0701, 01), "RDUBR", IO_RDUBR, io_rdubr, IF_IO) /* DI PAG, */ + iodef(IOINOP(0701, 02), "CLRPT", IO_CLRPT, io_clrpt, IF_IO) /* BO PAG, */ + iodef(IOINOP(0701, 03), "WRUBR", IO_WRUBR, io_wrubr, IF_IO) /* DO PAG, */ + iodef(IOINOP(0701, 04), "WREBR", IO_WREBR, io_wrebr, IF_IO) /* CO PAG, */ + iodef(IOINOP(0701, 05), "RDEBR", IO_RDEBR, io_rdebr, IF_IO) /* CI PAG, */ +# if KLH10_CPU_KL + iodef(IOINOP(0701, 06), NULL, IO_SZ_PAG, io_sz_pag, IF_IO) /* SZ PAG, */ + iodef(IOINOP(0701, 07), NULL, IO_SO_PAG, io_so_pag, IF_IO) /* SO PAG, */ +# endif +#endif /* KS || KL */ + + /* CCA */ +#if KLH10_CPU_KL + iodef(IOINOP(0701, 010), NULL, IO_BI_CCA,io_swp, IF_IO) /* BI CCA, */ + iodef(IOINOP(0701, 011), "SWPIA", IO_SWPIA, io_swpia, IF_IO) /* DI CCA, */ + iodef(IOINOP(0701, 012), "SWPVA", IO_SWPVA, io_swpva, IF_IO) /* BO CCA, */ + iodef(IOINOP(0701, 013), "SWPUA", IO_SWPUA, io_swpua, IF_IO) /* DO CCA, */ + iodef(IOINOP(0701, 014), NULL, IO_CO_CCA,io_swp, IF_IO) /* CO CCA, */ + iodef(IOINOP(0701, 015), "SWPIO", IO_SWPIO, io_swpio, IF_IO) /* CI CCA, */ + iodef(IOINOP(0701, 016), "SWPVO", IO_SWPVO, io_swpvo, IF_IO) /* SZ CCA, */ + iodef(IOINOP(0701, 017), "SWPUO", IO_SWPUO, io_swpuo, IF_IO) /* SO CCA, */ +#elif KLH10_SYS_ITS && KLH10_CPU_KS + iodef(IOINOP(0701, 011), "RDPCST",IO_RDPCST,io_rdpcst,IF_IO) /* DI CCA, */ + iodef(IOINOP(0701, 013), "WRPCST",IO_WRPCST,io_wrpcst,IF_IO) /* DO CCA, */ +#endif + + /* TIM */ +#if KLH10_CPU_KS +# if KLH10_PAG_ITS + iodef(IOINOP(0702, 0), "SDBR1", IO_SDBR1, io_sdbr1, IF_IO) /* BI TIM, */ + iodef(IOINOP(0702, 01), "SDBR2", IO_SDBR2, io_sdbr2, IF_IO) /* DI TIM, */ + iodef(IOINOP(0702, 02), "SDBR3", IO_SDBR3, io_sdbr3, IF_IO) /* BO TIM, */ + iodef(IOINOP(0702, 03), "SDBR4", IO_SDBR4, io_sdbr4, IF_IO) /* DO TIM, */ + iodef(IOINOP(0702, 07), "SPM", IO_SPM, io_spm, IF_IO) /* SO TIM, */ +# elif KLH10_PAG_KL /* DEC-specific hacks */ + iodef(IOINOP(0702, 0), "RDSPB", IO_RDSPB, io_rdspb, IF_IO) /* BI TIM, */ + iodef(IOINOP(0702, 01), "RDCSB", IO_RDCSB, io_rdcsb, IF_IO) /* DI TIM, */ + iodef(IOINOP(0702, 02), "RDPUR", IO_RDPUR, io_rdpur, IF_IO) /* BO TIM, */ + iodef(IOINOP(0702, 03), "RDCSTM",IO_RDCSTM,io_rdcstm,IF_IO) /* DO TIM, */ +# endif + iodef(IOINOP(0702, 04), "RDTIM", IO_RDTIM, io_rdtim, IF_IO) /* CO TIM, */ + iodef(IOINOP(0702, 05), "RDINT", IO_RDINT, io_rdint, IF_IO) /* CI TIM, */ + iodef(IOINOP(0702, 06), "RDHSB", IO_RDHSB, io_rdhsb, IF_IO) /* SZ TIM, */ +#endif /* KS */ +#if KLH10_CPU_KL + iodef(IOINOP(0702, 0), "RDPERF", IO_RDPERF, io_rdperf, IF_IO) /* BI TIM, */ + iodef(IOINOP(0702, 01), "RDTIME", IO_RDTIME, io_rdtime, IF_IO) /* DI TIM, */ + iodef(IOINOP(0702, 02), "WRPAE", IO_WRPAE, io_wrpae, IF_IO) /* BO TIM, */ +# if 0 + iodef(IOINOP(0702, 03), NULL, IO_DO_TIM, NULL, IF_IO) /* DO TIM, */ +# endif + iodef(IOINOP(0702, 04), NULL, IO_CO_TIM, io_co_tim, IF_IO) /* CO TIM, */ + iodef(IOINOP(0702, 05), NULL, IO_CI_TIM, io_ci_tim, IF_IO) /* CI TIM, */ + iodef(IOINOP(0702, 06), NULL, IO_SZ_TIM, io_sz_tim, IF_IO) /* SZ TIM, */ + iodef(IOINOP(0702, 07), NULL, IO_SO_TIM, io_so_tim, IF_IO) /* SO TIM, */ +#endif /* KL */ + + /* MTR */ +#if KLH10_CPU_KS +# if KLH10_PAG_ITS + iodef(IOINOP(0702, 010), "LDBR1", IO_LDBR1, io_ldbr1, IF_IO) /* BI MTR, */ + iodef(IOINOP(0702, 011), "LDBR2", IO_LDBR2, io_ldbr2, IF_IO) /* DI MTR, */ + iodef(IOINOP(0702, 012), "LDBR3", IO_LDBR3, io_ldbr3, IF_IO) /* BO MTR, */ + iodef(IOINOP(0702, 013), "LDBR4", IO_LDBR4, io_ldbr4, IF_IO) /* DO MTR, */ + iodef(IOINOP(0702, 017), "LPMR", IO_LPMR, io_lpmr, IF_IO) /* SO MTR, */ +# elif KLH10_PAG_KL /* DEC-specific hacks */ + iodef(IOINOP(0702, 010), "WRSPB", IO_WRSPB, io_wrspb, IF_IO) /* BI MTR, */ + iodef(IOINOP(0702, 011), "WRCSB", IO_WRCSB, io_wrcsb, IF_IO) /* DI MTR, */ + iodef(IOINOP(0702, 012), "WRPUR", IO_WRPUR, io_wrpur, IF_IO) /* BO MTR, */ + iodef(IOINOP(0702, 013), "WRCSTM",IO_WRCSTM,io_wrcstm,IF_IO) /* DO MTR, */ +# endif + iodef(IOINOP(0702, 014), "WRTIM", IO_WRTIM, io_wrtim, IF_IO) /* CO MTR, */ + iodef(IOINOP(0702, 015), "WRINT", IO_WRINT, io_wrint, IF_IO) /* CI MTR, */ + iodef(IOINOP(0702, 016), "WRHSB", IO_WRHSB, io_wrhsb, IF_IO) /* SZ MTR, */ +#endif /* KS */ +#if KLH10_CPU_KL + iodef(IOINOP(0702, 010), "RDMACT",IO_RDMACT, io_rdmact, IF_IO) /* BI MTR, */ + iodef(IOINOP(0702, 011), "RDEACT",IO_RDEACT, io_rdeact, IF_IO) /* DI MTR, */ +# if 0 + iodef(IOINOP(0702, 012), NULL, IO_BO_MTR, NULL, IF_IO) /* BO MTR, */ + iodef(IOINOP(0702, 013), NULL, IO_DO_MTR, NULL, IF_IO) /* DO MTR, */ +# endif + iodef(IOINOP(0702, 014), "WRTIME",IO_WRTIME, io_wrtime, IF_IO) /* CO MTR, */ + iodef(IOINOP(0702, 015), NULL, IO_CI_MTR, io_ci_mtr, IF_IO) /* CI MTR, */ + iodef(IOINOP(0702, 016), NULL, IO_SZ_MTR, io_sz_mtr, IF_IO) /* SZ MTR, */ + iodef(IOINOP(0702, 017), NULL, IO_SO_MTR, io_so_mtr, IF_IO) /* SO MTR, */ +#endif /* KL */ + +/* NOTE!! Check the IO_N def in opdefs.h if more IO-class ops are added! */ + +/* EXTENDED INSTRUCTIONS */ + +#if (KLH10_CPU_KS || KLH10_CPU_KL) \ + && (KLH10_SYS_T10 || KLH10_SYS_T20) /* DEC systems only */ +ixdef(IXOP(000), "ILLEG", IX_ILLEG, ix_undef, IF_SPEC) +ixdef(IXOP(001), "CMPSL", IX_CMPSL, ix_cmps, IF_SPEC) /* Common rtn */ +ixdef(IXOP(002), "CMPSE", IX_CMPSE, ix_cmps, IF_SPEC) /* " */ +ixdef(IXOP(003), "CMPSLE", IX_CMPSLE, ix_cmps, IF_SPEC) /* " */ +ixdef(IXOP(004), "EDIT", IX_EDIT, ix_edit, IF_SPEC) +ixdef(IXOP(005), "CMPSGE", IX_CMPSGE, ix_cmps, IF_SPEC) /* Common rtn */ +ixdef(IXOP(006), "CMPSN", IX_CMPSN, ix_cmps, IF_SPEC) /* " */ +ixdef(IXOP(007), "CMPSG", IX_CMPSG, ix_cmps, IF_SPEC) /* " */ +ixdef(IXOP(010), "CVTDBO", IX_CVTDBO, ix_cvtdb, IF_SPEC) /* Common rtn */ +ixdef(IXOP(011), "CVTDBT", IX_CVTDBT, ix_cvtdb, IF_SPEC) /* " */ +ixdef(IXOP(012), "CVTBDO", IX_CVTBDO, ix_cvtbd, IF_SPEC) /* Common rtn */ +ixdef(IXOP(013), "CVTBDT", IX_CVTBDT, ix_cvtbd, IF_SPEC) /* " */ +ixdef(IXOP(014), "MOVSO", IX_MOVSO, ix_movso, IF_SPEC) +ixdef(IXOP(015), "MOVST", IX_MOVST, ix_movst, IF_SPEC) +ixdef(IXOP(016), "MOVSLJ", IX_MOVSLJ, ix_movslj,IF_SPEC) +ixdef(IXOP(017), "MOVSRJ", IX_MOVSRJ, ix_movsrj,IF_SPEC) +#if KLH10_CPU_KLX /* Always MUUO if not on extended KL */ + ixdef(IXOP(020), "XBLT", IX_XBLT, ix_xblt, IF_SPEC) +#endif +#if KLH10_CPU_KL + ixdef(IXOP(021), "GSNGL", IX_GSNGL, ix_gsngl, IF_SPEC) + ixdef(IXOP(022), "GDBLE", IX_GDBLE, ix_gdble, IF_SPEC) +# if 0 /* Simulated in T10+T20 monitor */ + ixdef(IXOP(023), "GDFIX", IX_GDFIX, ix_gdfix, IF_SPEC) + ixdef(IXOP(024), "GFIX", IX_GFIX, ix_gfix, IF_SPEC) + ixdef(IXOP(025), "GDFIXR", IX_GDFIXR, ix_gdfixr,IF_SPEC) + ixdef(IXOP(026), "GFIXR", IX_GFIXR, ix_gfixr, IF_SPEC) +# endif /* 0 */ + ixdef(IXOP(027), "DGFLTR", IX_DGFLTR, ix_dgfltr,IF_SPEC) + ixdef(IXOP(030), "GFLTR", IX_GFLTR, ix_gfltr, IF_SPEC) + ixdef(IXOP(031), "GFSC", IX_GFSC, ix_gfsc, IF_SPEC) +#endif /* KL only */ +#endif /* (KL||KS) && (T10||T20) */ + +/* NOTE!! Check the IX_N def in opdefs.h if more EXTEND ops are added! */ + +/* Other cruft for dubious posterity */ + +/* +;OLD PROGRAMS USE THESE NAMES + +CLEAR==SETZ +CLEARI==SETZI +CLEARM==SETZM +CLEARB==SETZB + +;RANDOM ALIAS NAMES + +ERJMP==JUMP 16, ; TOPS-20 JSYS-error dispatch (becomes JRST) +ERCAL==JUMP 17, ; TOPS-20 JSYS-error call (becomes PUSHJ 17,) +ADJBP==IBP ;KL10 FORM OF IBP WITH VARIABLE NUMBER TO INCREMENT +JFOV==JFCL 1, ;PDP10 INSTRUCTION (PC CHANGE ON PDP6) +JCRY1==JFCL 2, +JCRY0==JFCL 4, +JCRY==JFCL 6, +JOV==JFCL 10, +PORTAL==JRST 1, ; KI/KL +JRSTF==JRST 2, +HALT==JRST 4, +XJRSTF==JRST 5, ; KL/KS +XJEN==JRST 6, ; KL/KS +XPCW==JRST 7, ; KL/KS +JEN==JRST 12, +SFM==JRST 14, ; KL/KS +XMOVEI==SETMI ; KL only +XHLLI==HLLI ; KL only + +;PDP6 HAS LONG FORM ROUNDED INSTEAD OF IMMEDIATES (FADRL, not FADRI, etc) + +*/ diff --git a/src/opdata.c b/src/opdata.c new file mode 100644 index 0000000..e8584b0 --- /dev/null +++ b/src/opdata.c @@ -0,0 +1,166 @@ +/* OPDATA.C - Holds data definitions and instruction opcode tables. +*/ +/* $Id: opdata.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: opdata.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#define EXTDEF /* Definitions, not declarations! */ + +#include /* For NULL */ +#include /* For op_init error reporting */ + +#include "klh10.h" +#include "kn10def.h" +#include "opdefs.h" + +#ifdef RCSID + RCSID(opdata_c,"$Id: opdata.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Initialize master table at compile time. +** op_init() uses this data to fill out all other tables at runtime. +*/ +struct opdef opclist[] = { +# define idef(i, nam, en, rtn, fl) {nam, i, fl, (opfp_t) rtn }, +# define iodef(i, nam, en, rtn, fl) {nam, i, fl, (opfp_t) rtn }, +# define ixdef(i, nam, en, rtn, fl) {nam, i, fl, (opfp_t) rtn }, +# include "opcods.h" +# undef idef +# undef iodef +# undef ixdef + {0, -1} /* Ends with null name, -1 value. */ +}; + +/* Made-up default defs not in master list */ +static struct opdef opdef_muuo = + { "MUUO", 0, (IF_1X1|IF_OPN), (opfp_t) i_muuo }; +static struct opdef opdef_luuo = + { "LUUO", 0, (IF_1X1|IF_OPN), (opfp_t) i_luuo }; +static struct opdef opdef_ixdflt = + {"X_UNDEF", 0, (IF_SPEC), (opfp_t) ix_undef }; +static struct opdef opdef_iodflt = + { "IO", 0, (IF_IO), (opfp_t) NULL }; +#if !KLH10_CPU_KS +static struct opdef opdef_diodflt = + {"DIO", 0, (IF_IO), (opfp_t) i_diodisp }; +#endif /* !KS */ + + +/* Old-IO instruction names, indexed by IOX_ */ +char *opcionam[8] = { + "BLKI", "DATAI", "BLKO", "DATAO", + "CONO", "CONI", "CONSZ", "CONSO" +}; +int opcioflg[8] = { + IF_IO|IF_SPEC|IF_MS|IF_M1, /* BLKI data -> @ ptr in c(E) */ + IF_IO|IF_MW|IF_M1, /* DATAI data -> c(E) */ + IF_IO|IF_SPEC|IF_MS|IF_M1, /* BLKO @ ptr in c(E) -> data */ + IF_IO|IF_MR|IF_M1, /* DATAO c(E) -> data */ + IF_IO|IF_ME, /* CONO E -> device */ + IF_IO|IF_MW|IF_M1, /* CONI device -> c(E) */ + IF_IO|IF_SKP|IF_ME, /* CONSZ !(device & E) */ + IF_IO|IF_SKP|IF_ME /* CONSO (device & E) */ +}; + +char *opcdvnam[128] = { + "APR", /* 000 */ + "PI", /* 004 */ + "PAG", /* 010 */ + "CCA", /* 014 */ + "TIM", /* 020 */ + "MTR", /* 024 */ + /* 030,034,040,044,050,054,060,064,070,074 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + "PTP", /* 100 */ + "PTR", /* 104 */ + 0, /* 110 */ + 0, /* 114 */ + "TTY", /* 120 */ + 0, /* 124 */ + "DIS" /* 130 */ +}; + +/* OP_INIT - Initialize PDP10 instruction op data +** The master table is built up at compile time from the definitions +** in opdefs.h and opcods.h. The actual tables needed for runtime +** dispatch operation are too complicated for simple initialization, so +** this is where they're set up at runtime. +*/ +int +op_init(void) +{ + register struct opdef *op; + register int i; + int errs = 0; + + /* First set all dispatch tables to default values */ + + /* Primary dispatch for normal ops */ + for (op = &opdef_muuo, i = 0; i < I_N; ++i) { /* 0-777 = MUUO */ + cpu.opdisp[i] = op->oprtn; + opcptr[i] = op; + } + /* Modify primary table so 001-037 become LUUOs */ + for (op = &opdef_luuo, i = 1; i <= 037; ++i) { + cpu.opdisp[i] = op->oprtn; + opcptr[i] = op; + } +#if !KLH10_CPU_KS + /* Modify primary table so 0700-0777 become dev-IO instructions */ + for (op = &opdef_diodflt, i = 0700; i <= 0777; ++i) { + cpu.opdisp[i] = op->oprtn; + opcptr[i] = op; + } +#endif /* !KS */ + + /* Secondary dispatch for EXTEND ops */ + for (op = &opdef_ixdflt, i = 0; i < IX_N; ++i) { /* Extend ops */ + opcxrtn[i] = (opxfp_t) (op->oprtn); + opcxptr[i] = op; + } + + /* Secondary dispatch for IO ops */ + for (op = &opdef_iodflt, i = 0; i < IO_N; ++i) { /* IO ops */ + opciortn[i] = (opiofp_t) (op->oprtn); + opcioptr[i] = op; + } + + /* Now set up values from compile-time data in master list */ + + for(op = opclist, i = 0; (op->opstr || op->opval != -1); ++op, ++i) { + if (0 <= op->opval && op->opval < I_N) { + cpu.opdisp[op->opval] = op->oprtn; /* Normal op */ + opcptr[op->opval] = op; + } else if (IXOP(0) <= op->opval && op->opval <= IXOP(IX_N-1)) { + opcxrtn[IXIDX(op->opval)] = (opxfp_t) (op->oprtn); /* EXTEND op */ + opcxptr[IXIDX(op->opval)] = op; + } else if (IOEXOP(0,0) <= op->opval + && op->opval <= IOEXOP(IOX_N-1,IODV_N-1)) { + opciortn[IOIDX(op->opval)] = (opiofp_t)(op->oprtn); /* IO op */ + opcioptr[IOIDX(op->opval)] = op; + } else { + fprintf(stderr, "op_init: Illegal opcode! Def %d:(%#o, \"%s\")\n", + i, op->opval, (op->opstr ? op->opstr : "")); + ++errs; + } + } + return (errs ? 0 : 1); +} diff --git a/src/opdefs.h b/src/opdefs.h new file mode 100644 index 0000000..16c77fc --- /dev/null +++ b/src/opdefs.h @@ -0,0 +1,285 @@ +/* OPDEFS.H - Define PDP-10 instruction opcodes and declare routines. +*/ +/* $Id: opdefs.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: opdefs.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#ifndef OPDEFS_INCLUDED +#define OPDEFS_INCLUDED 1 + +/* See also the RCSID for opcods.h after its first inclusion */ +#ifdef RCSID + RCSID(opdefs_h,"$Id: opdefs.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +#ifndef EXTDEF +# define EXTDEF extern /* Default is just to declare vars */ +#endif + +#define I_N 01000 /* Max # of PDP-10 opcodes */ +#define IO_N (16*3) /* Max # of IO instr opcodes (true max 02000) */ +#define IX_N 040 /* Max # of EXTEND opcodes */ + +/* Opcode flags +** IF_{A/M}{R/W}{/2/4}/{RI/RI8/RIF} +** A/M - AC or Mem or {Immediate, Imm-8, Imm-float} +** R/W - Read or Write (or both) operand +** /2/4 - 1, 2, or 4 word op/result +*/ + +/* Mem Operand flags */ +#define IF_MFOFF 0 /* Offset of Mem operand type field */ +#define IF_MFMASK (017< ? */ +#define IF_X1 (IF_MR|IF_M1) /* c(E) -> ? */ +#define IF_1X1 (IF_AR|IF_A1|IF_MR|IF_M1) /* c(AC) op c(E) -> ? */ + +#define IF_1X (IF_AS|IF_A1|IF_MR|IF_M1) /* c(AC) op c(E) -> c(AC) */ +#define IF_1XI (IF_AS|IF_A1|IF_MR|IF_ME) /* c(AC) op E -> c(AC) */ +#define IF_1XM (IF_AR|IF_A1|IF_MS|IF_M1) /* c(AC) op c(E) -> c(E) */ +#define IF_1XB (IF_AS|IF_A1|IF_MS|IF_M1) /* c(AC) op c(E) -> c(AC,E) */ + +#define IF_1XFI (IF_AS|IF_A1|IF_MR|IF_MEF) /* c(AC) op f(E,0) -> c(AC) */ +#define IF_1XFL (IF_AS|IF_A2|IF_MR|IF_M1) /* c(AC) op c(E) -> c(AC2) */ + +#define IF_1S (IF_AW|IF_A1|IF_MR|IF_M1) /* op c(E) -> c(AC) */ +#define IF_1SI (IF_AW|IF_A1|IF_ME) /* op E -> c(AC) */ +#define IF_1SM (IF_AR|IF_A1|IF_MW|IF_M1) /* op c(AC) -> c(E) */ +#define IF_1SS (IF_AW|IF_A0|IF_MS|IF_M1) /* op c(E) -> c(E) */ + /* (if AC!=0, -> c(AC) too) */ +#define IF_1SB (IF_AW|IF_A1|IF_MS|IF_M1) /* op c(E) -> c(AC), c(E) */ + +#define IF_1C (IF_AW|IF_A1) /* op const -> c(AC) */ +#define IF_1CM (IF_MW|IF_M1) /* op const -> c(E) */ +#define IF_1CB (IF_MW|IF_M1) /* op const -> c(AC),c(E) */ + +#define IF_2X (IF_AS|IF_A2|IF_MR|IF_M2) /* c(AC2) op c(E2) -> c(AC2) */ +#define IF_2S (IF_AW|IF_A2|IF_MR|IF_M2) /* op c(E2) -> c(AC2) */ +#define IF_2SM (IF_AR|IF_A2|IF_MW|IF_M2) /* op c(AC2) -> c(E2) */ + +#define IF_NOP (IF_A1) /* NOP must have some flag set to avoid + ** use of default flags, so use this one + ** without an AC R/W flag. */ + +/* Normal instruction defs */ + + /* None - the 9-bit opcode is used, represented as octal */ + + +/* EXTEND instruction defs */ + +/* Macro to define value of an IX_ EXTEND instruction opcode */ +#define IXOP(op) ((op)|01000) /* Make up an extended opcode */ +#define IXIDX(xop) ((xop)&0777) /* Cvt IX_x opcode to dispatch index */ + + +/* IO instruction defs */ + +/* Macros to define value of an IO_ IO instruction opcode. +** IOINOP (IO INternal OP) defines it as AC-dispatched from a normal op. +** IOEXOP (IO EXternal OP) defines it as an " ," instruction. +** +** Either can generate the same value, the difference is only one of +** representation. Both use a max of 15 bits, so no special type is needed. +*/ +#define IOINOP(op, ac) (((op)<<6)|((ac)<<2)) /* Use normal op plus AC */ +#define IOEXOP(iox, dev) \ + ((0700<<6)|((iox)<<2)|((dev)<<5)) /* Use IOX_ plus ext device */ + +#define IOIDX(io) (((io)>>2)&01777) /* Cvt IO_x opcode to dispatch index */ + +/* Indices for Dev-IO instructions */ +enum { + IOX_BLKI=0, /* IOEXOP(IOX_BLKI, 0) == IOINOP(0700,0) == 070000 */ + IOX_DATAI, /* IOEXOP(IOX_DATAI,0) == IOINOP(0700,1) == 070004 */ + IOX_BLKO, /* IOEXOP(IOX_BLKO, 0) == IOINOP(0700,2) == 070010 */ + IOX_DATAO, /* IOEXOP(IOX_DATAO,0) == IOINOP(0700,3) == 070014 */ + IOX_CONO, /* IOEXOP(IOX_CONO, 0) == IOINOP(0700,4) == 070020 */ + IOX_CONI, /* IOEXOP(IOX_CONI, 0) == IOINOP(0700,5) == 070024 */ + IOX_CONSZ, /* IOEXOP(IOX_CONSZ,0) == IOINOP(0700,6) == 070030 */ + IOX_CONSO, /* IOEXOP(IOX_CONSO,0) == IOINOP(0700,7) == 070034 */ + IOX_N +}; + +extern char *opcionam[IOX_N]; /* Names for dev-style IO instrs */ +extern int opcioflg[IOX_N]; /* IF_ flags for ditto */ + + +/* Conventional values for PDP-10 I/O devices, +** provided here so they can be used with IOEXOP. +*/ + /* Internal "devices" */ +#define IODV_APR (000>>2) /* Processor */ +#define IODV_PI (004>>2) /* PI system */ +#define IODV_PAG (010>>2) /* KI+: Pager */ +#define IODV_CCA (014>>2) /* KL: Cache */ +#define IODV_TIM (020>>2) /* KL: Timers, KS: Read various */ +#define IODV_MTR (024>>2) /* KL: Meters, KS: Write ditto */ + + /* External devices - old-style IO */ +#define IODV_CLK (0070>>2) /* DKN10 clock (KA/KI) */ +#define IODV_PTP (0100>>2) /* Paper-Tape Punch */ +#define IODV_PTR (0104>>2) /* Paper-Tape Reader */ +#define IODV_TTY (0120>>2) /* Console TTY */ +#define IODV_DIS (0130>>2) /* Ye 340 display */ + +#define IODV_N (01000>>2) /* Max # of IO devices (128) */ + +extern char *opcdvnam[IODV_N]; /* Names for IO devices */ + +/* Declare instruction routines */ + +/* Instruction routine definition macros. +** For consistency and the ability to globally control all instruction +** routine setups, these macros must ALWAYS be used when defining +** instruction execution routines. +** insdef - for normal instructions +** xinsdef - for EXTEND instructions (takes additional E1 arg) +** ioinsdef - for new-IO instructions (dispatched by AC; only E arg provided) +** All instruction routines must return one of +** PCINC_0 (jumped), PCINC_1 (normal), or PCINC_2 (skipped). +*/ +#define insdef(rtn) pcinc_t rtn(int op, int ac, register vaddr_t e) +#define xinsdef(rtn) pcinc_t rtn(int xop, int ac, vaddr_t e0, vaddr_t e1) +#define ioinsdef(rtn) pcinc_t rtn(register vaddr_t e) + +typedef pcinc_t (*opfp_t)(int, int, vaddr_t); /* Normal instr */ +typedef pcinc_t (*opxfp_t)(int, int, vaddr_t, vaddr_t); /* Extended instr */ +typedef pcinc_t (*opiofp_t)(vaddr_t); /* I/O instr */ + +extern pcinc_t /* For now, bypass include ordering probs */ + ix_undef(int, int, vaddr_t, vaddr_t), + i_diodisp(int, int, vaddr_t) +# define idef(i, nam, en, rtn, fl) , rtn(int, int, vaddr_t) +# define ixdef(i, nam, en, rtn, fl) , rtn(int, int, vaddr_t, vaddr_t) +# define iodef(i, nam, en, rtn, fl) , rtn(vaddr_t) +# include "opcods.h" +# undef idef +# undef ixdef +# undef iodef + ; + +/* Define I_xxx enums so can easily refer to specific opcodes */ +enum opcode { + I_0 = 0 +# define idef(i, nam, en, rtn, fl) , en=i +# define iodef(i, nam, en, rtn, fl) , en=i +# define ixdef(i, nam, en, rtn, fl) , en=i +# include "opcods.h" +# undef idef +# undef iodef +# undef ixdef +}; + +extern struct opdef { /* Master table */ + char *opstr; + int opval, opflg; + opfp_t oprtn; /* Function pointer */ +} opclist[]; + +/* Declare tables that will hold opcode info, indexed by I_xxx enum. +** These are initialized at runtime from the master opclist. +** Note that the normal i_xxx dispatch table now lives in "cpu" to +** help promote locality. +*/ +#if 0 +EXTDEF opfp_t opdisp[I_N]; /* I_xxx Routine dispatch table */ +#endif +EXTDEF opxfp_t opcxrtn[IX_N]; /* IX_xxx routine dispatch */ +EXTDEF opiofp_t opciortn[IO_N]; /* IO_xxx routine dispatch */ + +EXTDEF struct opdef /* Pointers to full definitions */ + *opcptr[I_N], /* back in master list */ + *opcxptr[IX_N], + *opcioptr[IO_N]; + +extern int op_init(void); /* Function to initialize runtime tables */ + +/* RCSID expressed here on behalf of opcods.h which cannot do it itself. + */ +#if defined(OPCODS_RCSID) && defined(RCSID) + OPCODS_RCSID +#endif + +/* Macro for invoking normal instruction */ +#define op_xct(op, ac, e) \ + (*cpu.opdisp[(int)(op)])((int)(op), (int)(ac), (vaddr_t)(e)) + +/* Instruction word format defs - here for now */ + +/* Define fields of instruction word */ +#define IW_OP ((h10_t)0777<<9) /* LH: Opcode */ +#define IW_AC (AC_MASK<<5) /* LH: AC field */ +#define IW_I 020 /* LH: Indirect bit */ +#define IW_X AC_MASK /* LH: Index register AC */ +#define IW_Y H10MASK /* RH: Y address */ + +#define IW_EI 0200000 /* LH: EFIW Indirect bit */ +#define IW_EX 0170000 /* LH: EFIW Index register */ +#define IW_EY MASK30 /* LH+RH: EFIW Y address */ + +/* Get various fields of instruction word */ +#define iw_op(w) (LHGET(w)>>9) /* Get opcode, assumes word is OK */ +#define iw_ac(w) ((LHGET(w)>>5)&AC_MASK) +#define iw_i(w) (LHGET(w)&IW_I) /* Note result not right-justified! */ +#define iw_x(w) (LHGET(w)&IW_X) +#define iw_y(w) RHGET(w) + +#define iw_ei(w) (LHGET(w)&IW_EI) /* Note result not right-justified! */ +#define iw_ex(w) ((LHGET(w)>>12)&AC_MASK) +/*define iw_ey(w) */ + +#endif /* ifndef OPDEFS_INCLUDED */ diff --git a/src/osdnet.c b/src/osdnet.c new file mode 100644 index 0000000..d76b9ee --- /dev/null +++ b/src/osdnet.c @@ -0,0 +1,2554 @@ +/* OSDNET.C - OS Dependent Network facilities +*/ +/* $Id: osdnet.c,v 2.6 2001/11/19 10:32:34 klh Exp $ +*/ +/* Copyright © 1999, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: osdnet.c,v $ + * Revision 2.6 2001/11/19 10:32:34 klh + * Solaris port fixups. + * + * Revision 2.5 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* This file, like DPSUP.C, is intended to be included directly + into source code rather than compiled separately and then linked + together. The reason for this is that the actual configuration + of code will vary depending on its intended use, so a single .o + file cannot satisfy all programs. + */ + +#include /* For basic Unix syscalls */ + +/* The possible configuration macro definitions, with defaults: + */ +#ifndef NETIFC_MAX +# define NETIFC_MAX 20 +#endif + +#ifndef OSDNET_INCLUDED +# include "osdnet.h" /* Insurance to make sure our defs are there */ +#endif + +#ifdef RCSID + RCSID(osdnet_c,"$Id: osdnet.c,v 2.6 2001/11/19 10:32:34 klh Exp $") +#endif + +/* Local predeclarations */ + +struct ifent *osn_iflookup(char *ifnam); +int osn_ifealookup(char *ifnam, unsigned char *eap); + +/* Get a socket descriptor suitable for general net interface + examination and manipulation; this is not necessarily suitable for + use as a packetfilter. + This may only make sense on Unix. + */ +int +osn_ifsock(char *ifnam, ossock_t *as) +{ +#if (KLH10_NET_NIT || KLH10_NET_DLPI || KLH10_NET_BPF || KLH10_NET_PFLT || \ + KLH10_NET_TUN || KLH10_NET_LNX) + return ((*as = socket(AF_INET, SOCK_DGRAM, 0)) >= 0); +#else +# error OSD implementation needed for osn_ifsock +#endif +} + +int +osn_ifclose(ossock_t s) +{ +#if (KLH10_NET_NIT || KLH10_NET_DLPI || KLH10_NET_BPF || KLH10_NET_PFLT || \ + KLH10_NET_TUN || KLH10_NET_LNX) + return (close(s) >= 0); +#else +# error OSD implementation needed for osn_ifclose +#endif +} + + +/* Minor utilities */ + +char * +eth_adrsprint(char *cp, unsigned char *ea) +{ + sprintf(cp, "%x:%x:%x:%x:%x:%x", ea[0], ea[1], ea[2], ea[3], ea[4], ea[5]); + return cp; +} + +char * +ip_adrsprint(char *cp, unsigned char *ia) +{ + sprintf(cp, "%d.%d.%d.%d", ia[0], ia[1], ia[2], ia[3]); + return cp; +} + + +/* Interface Table initialization +** Gets info about all net interfaces known to the native system. +** Used for several purposes, hence fairly general. +** +*/ +/* + SIOCGIFCONF documentation is virtually non-existent. The following + info comes from looking at source code. + struct ifconf ifconf; + s = socket(AF_INET, SOCK_DGRAM, 0); + ioctl(s, SIOCGIFCONF, &ifconf); + + ifconf has only two elements: + : struct ifconf + caddr_t ifcu_buf; Buffer to use + int ifc_len; Buffer size in bytes (or # bytes used) + + On return, the buffer holds a series of "struct ifreq" entries, each of + which holds an interface name and address. On older systems, each + such entry was a fixed size, but this was later changed in order to + accomodate longer addresses and consequently, + =====>> EACH ENTRY MAY BE OF VARYING SIZE!!!! <<====== + + This, by the way, is why SIOCGIFCONF appeared to be broken on OSF/1 when + used with a single ifreq's worth of buffer -- since it needed a larger + "ifreq" than the buffer had room for, it returned nothing. + + Although the second element of "struct ifreq" is a union, SIOCGIFCONF always + uses it to return a sockaddr: + : struct ifreq + char ifr_name[16] interface name, eg "de0" + struct sockaddr ifr_addr interface address + : struct sockaddr + unsigned char sa_len # bytes in this sockaddr + unsigned char sa_family address type, AF_xxx + char sa_data[14] actually up to 253 bytes! + + Note that the sa_len field is new; older versions of sockaddr had + just sa_family and sa_data. + + To repeat, the actual size of an ifreq depends on the size of the + sockaddr it contains. This is ALWAYS at least sizeof(struct sockaddr), + so trickiness is only needed when sa_len is greater than this default + size. + +#define _SIZEOF_ADDR_IFREQ(p) \ + ((p)->ifr_addr.sa_len <= sizeof(struct sockaddr) \ + ? sizeof(struct ifreq) \ + : (sizeof(struct ifreq)-sizeof(struct sockaddr)+(p)->ifr_addr.sa_len)) + +or alternatively: + +#define _SIZEOF_ADDR_IFREQ(ifr) \ + ((ifr).ifr_addr.sa_len > sizeof(struct sockaddr) \ + ? (sizeof(struct ifreq) - sizeof(struct sockaddr) + (ifr).ifr_addr.sa_len) \ + : sizeof(struct ifreq)) + +*/ +/* + Note that searching for AF_INET or IP addresses only finds interfaces that + are presently configured with IP addresses by "ifconfig", typically those + that are also up. An interface that is dedicated to the emulator will + normally be both "down" and have no IP address bound to it. + + Another way to look at this is that until an interface is configured + up, it has no IP address bound to it. This is the same reason that + SIOCGIFADDR cannot be used to find the IP address of a dedicated + interface; there is none. +*/ + +/* Complete table as returned from system by SIOCGIFCONF + * Currently static for debugging, could be dynamic. + */ +static struct ifconf ifctab; +static char ifcbuf[sizeof(struct ifreq) * NETIFC_MAX]; + +/* Our own internal table of interface entries, filtered + to keep only the ones we're interested in. + */ +static int iftab_initf = 0; +static int iftab_nifs = 0; +static struct ifent iftab[NETIFC_MAX]; + +static void osn_iftab_pass(int opts, int npass, int s, struct ifconf *ifc); + +/* Get table of all interfaces, using our own generic entry format. + * Routine works as follows: + * + * First, grabs a IFCONF table of all interfaces from the kernel. + * Each IFR entry contains a name and an address. + * + * Second, scan this IFCONF table (pass 1) to build our own table of IFE + * entries, made of all IFR entries that pass the following filter + * flags: + * IFTAB_IPS - if set, accepts AF_INET interfaces (with IP address) + * IFTAB_ETHS - if set, accepts LINK interfaces with Ether-like addrs. + * IFTAB_OTH - if set, accepts all others (with unknown addr) + * + * Third, make another scan of IFCONF (pass 2) that grabs *all* information + * about any interface present in our IFE table, regardless of what caused + * it to be inserted. + */ +int +osn_iftab_init(int opts) +{ + int s; + struct ifconf *ifc = &ifctab; + register struct ifent *ifet; + + /* Start out with empty table */ + iftab_nifs = 0; + ifet = &iftab[0]; + + /* Open socket with AF_INET family to get at IP stuff. + */ + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + esfatal(1, "osn_iftab_init socket()"); + } + + /* Gobble table of config info about all interfaces, including name + and IP address. + */ + ifc->ifc_len = sizeof(ifcbuf); + ifc->ifc_buf = (caddr_t)ifcbuf; + if (ioctl(s, SIOCGIFCONF, (char *)ifc) < 0) { + close(s); + esfatal(1, "osn_iftab_init SIOCGIFCONF"); + } + if (ifc->ifc_len < sizeof(struct ifreq)) { + if (DP_DBGFLG) + dbprintln("SIOCGIFCONF only got %d bytes (need %d)", + (int)ifc->ifc_len, (int)sizeof(struct ifreq)); + close(s); + return 0; /* Assume no interfaces */ + } + if (DP_DBGFLG) + dbprintln("SIOCGIFCONF returned %d bytes (%d ifcs?)", + (int)ifc->ifc_len, (int)(ifc->ifc_len/sizeof(struct ifreq))); + + if (DP_DBGFLG) + osn_ifctab_show(stdout, ifc); + + /* Grabbed everything from OS, now grovel through it */ + osn_iftab_pass(opts, 1, s, ifc); /* Do pass 1 scan */ + osn_iftab_pass(opts, 2, s, ifc); /* Do pass 2 scan */ + iftab_initf = opts; /* Inited! */ + + close(s); + if (DP_DBGFLG) + osn_iftab_show(stdout, &iftab[0], iftab_nifs); + return iftab_nifs; +} + +int +osn_nifents(void) +{ + return iftab_nifs; +} + +static void +osn_iftab_pass(int opts, int npass, int s, struct ifconf *ifc) +{ + register int i; + int offset; + struct ifreq ifr; + register struct ifreq *ifp, *ifend, *ifnext; + register struct ifent *ife, *ifet; + + /* Start out with empty table */ + if (npass == 1) + iftab_nifs = 0; + + ifet = &iftab[0]; + + ifp = ifc->ifc_req; + ifend = (struct ifreq *)((char *)ifp + ifc->ifc_len); + for (; ifp < ifend; ifp = ifnext) { + + /* Find next pointer now, to simplify structure of code. + This is complicated by the fact that the returned table format + has changed under different OS versions. The "new regime" + uses a variable-size "ifreq" entry! Choke... + */ + ifnext = ifp + 1; /* Assume normal entry at first */ +#if NETIF_HAS_SALEN + if (ifp->ifr_addr.sa_len > sizeof(struct sockaddr)) { + offset = ifp->ifr_addr.sa_len - sizeof(struct sockaddr); + ifnext = (struct ifreq *)((char *)ifnext + offset); + } +#endif + /* See whether this entry is already in table. + */ + for (i = 0; i < iftab_nifs; ++i) { + if (strncmp(ifp->ifr_name, ifet[i].ife_name, sizeof(ifp->ifr_name)) + == 0) { + /* Found existing entry! Use its pointer */ + ife = &ifet[i]; + break; + } + } + if (i >= iftab_nifs) { + if (npass == 2) + continue; + /* Pass1, decide now whether to create entry or not */ + /* See if it's something we're interested in. */ + if ((opts & IFTAB_IPS) && (ifp->ifr_addr.sa_family == AF_INET)) { + /* yes */ +#if NETIF_HAS_ETHLINK + } else if ((opts & IFTAB_ETHS) + && (ifp->ifr_addr.sa_family == AF_LINK) + && (((struct sockaddr_dl *)&ifp->ifr_addr)->sdl_alen + == ETHER_ADRSIZ)){ + /* yes */ +#endif + } else if (opts & IFTAB_OTH) { + /* yes */ + } else { + /* Nope, ignore this one */ + continue; + } + + /* Yes, create this entry */ + if (iftab_nifs >= NETIFC_MAX-1) { + error("ifc table overflow, max %d", NETIFC_MAX); + return; /* Stop now */ + } + ife = &ifet[iftab_nifs++]; /* New table entry to fill out */ + + /* Copy interface name and ensure null-terminated + even if sizes someday get out of synch. + */ + strncpy(ife->ife_name, ifp->ifr_name, + (sizeof(ife->ife_name) < sizeof(ifp->ifr_name)) + ? sizeof(ife->ife_name) : sizeof(ifp->ifr_name)); + ife->ife_name[sizeof(ife->ife_name)-1] = '\0'; + + /* These ioctls should work for all interfaces */ + strncpy(ifr.ifr_name, ifp->ifr_name, sizeof(ifr.ifr_name)); + if (ioctl(s, SIOCGIFFLAGS, (char *)&ifr) < 0) { + syserr(errno, "SIOCGIFFLAGS for \"%s\"", ife->ife_name); + continue; /* Discard entry */ + } + ife->ife_flags = ifr.ifr_flags; + + /* SIOCGIFMTU: ifr_mtu */ + /* SIOCGIFMETRIC: ifr_metric */ + continue; + } + if (npass == 1) + continue; + + /* Pass 2 and dealing with an existing entry. Flesh it out! */ + switch (ifp->ifr_addr.sa_family) { + case AF_INET: + ife->ife_pinet = ifp; /* Remember pointer */ + ife->ife_ipint = ((struct sockaddr_in *) + &ifp->ifr_addr)->sin_addr.s_addr; + break; +#if NETIF_HAS_ETHLINK + case AF_LINK: + if (((struct sockaddr_dl *)&ifp->ifr_addr)->sdl_alen + == ETHER_ADRSIZ) { + /* Looks like Ethernet physical link, save addr */ + struct sockaddr_dl *dla = (struct sockaddr_dl *)&ifp->ifr_addr; + memcpy(ife->ife_ea, LLADDR(dla), dla->sdl_alen); + ife->ife_gotea = TRUE; + ife->ife_plink = ifp; + break; + } +#endif + /* Else drop through to "other" case */ + default: + ife->ife_pother = ifp; + break; + } + } +} + +void +osn_ifctab_show(FILE *f, struct ifconf *ifc) +{ + register struct ifreq *ifr; + struct ifreq *ifrend; + int len; + int i; + unsigned char *cp; + int nents = 0; + int nvary = 0; + + fprintf(f, "Interface table: %ld bytes (%d entries if std addr len %d)\n", + (long)ifc->ifc_len, ifc->ifc_len/sizeof(struct ifreq), + (int)sizeof(struct sockaddr)); + + ifr = ifc->ifc_req; + ifrend = (struct ifreq *)((char *)(ifc->ifc_req) + ifc->ifc_len); + for (i = 0; ifr < ifrend; ++i) { + ++nents; + +#if NETIF_HAS_SALEN + len = ifr->ifr_addr.sa_len; +#else + len = sizeof(struct sockaddr); +#endif + + /* Output entry data */ + fprintf(f, "%2d: \"%.*s\" fam %d, len %d", + i, (int)sizeof(ifr->ifr_name), ifr->ifr_name, + ifr->ifr_addr.sa_family, len); + if (len) { + cp = (unsigned char *) ifr->ifr_addr.sa_data; + fprintf(f, " = %x", *cp); + for (--len; len > 0; --len) { + fprintf(f, ":%x", *++cp); + } + } + fprintf(f, "\n"); + + cp = (unsigned char *) ifr->ifr_addr.sa_data; + switch (ifr->ifr_addr.sa_family) { + case AF_INET: + { + struct sockaddr_in *skin = (struct sockaddr_in *) &ifr->ifr_addr; + struct in_addr *in = &skin->sin_addr; + unsigned char *ucp = (unsigned char *) &in->s_addr; + + fprintf(f, " AF_INET = port %d, IP %d.%d.%d.%d\n", + (int)skin->sin_port, + ucp[0], ucp[1], ucp[2], ucp[3]); + } + break; + +#if NETIF_HAS_ETHLINK + case AF_LINK: + { + struct sockaddr_dl *dla = (struct sockaddr_dl *) &ifr->ifr_addr; + fprintf(f, " AF_LINK = type %d, alen %d", + dla->sdl_type, dla->sdl_alen); + if (len = dla->sdl_alen) { + cp = (unsigned char *) LLADDR(dla); + fprintf(f, " = %x", *cp); + for (--len; len > 0; --len) { + fprintf(f, ":%x", *++cp); + } + } + fprintf(f, "\n"); + } + break; +#endif + + default: + fprintf(f, " No handler for this family\n"); + } + + + /* Move onto next entry */ +#if NETIF_HAS_SALEN + if (ifr->ifr_addr.sa_len > sizeof(struct sockaddr)) { + ++nvary; + ifr = (struct ifreq *)((char *)(ifr + 1) + + (ifr->ifr_addr.sa_len - sizeof(struct sockaddr))); + } else +#endif + ifr++; + } + if (nvary) + fprintf(f, "Interface summary: %d entries of varying length\n", + nents); + else + fprintf(f, "Interface summary: %d entries of std length %d\n", + nents, (int)sizeof(struct ifreq)); +} + + +void +osn_iftab_show(FILE *f, struct ifent *ifents, int nents) +{ + register struct ifent *ife; + int i; + + fprintf(f, "Filtered IFE table: %d entries\n", nents); + + for (i = 0, ife = ifents; i < nents; ++i, ++ife) { + fprintf(f, "%2d: \"%s\"", i, ife->ife_name); + if (ife->ife_pinet) { + unsigned char *ucp = ife->ife_ipchr; + fprintf(f, " (IP %d.%d.%d.%d)", + ucp[0], ucp[1], ucp[2], ucp[3]); + } + if (ife->ife_plink || ife->ife_gotea) { + unsigned char *ucp = ife->ife_ea; + fprintf(f, " (%sEther %x:%x:%x:%x:%x:%x)", + (ife->ife_plink ? "" : "Extracted "), + ucp[0], ucp[1], ucp[2], ucp[3], ucp[4], ucp[5]); + } + if (ife->ife_pother) { + fprintf(f, " (Other: fam %d)", + ife->ife_pother->ifr_addr.sa_family); + } + fprintf(f, "\n"); + } +} + +/* OSN_IFTAB_ARP - Look up an entry ARP-style (given IP address) + */ +struct ifent * +osn_iftab_arp(struct in_addr ia) +{ + register int i = 0; + register struct ifent *ife = iftab; + + for (; i < iftab_nifs; ++i, ++ife) + if (ife->ife_ipia.s_addr == ia.s_addr) + return ife; + return NULL; +} + +/* OSN_IFTAB_ARPGET - As above, but returns EA if found. + */ +int +osn_iftab_arpget(struct in_addr ia, unsigned char *eap) +{ + register struct ifent *ife; + + if ((ife = osn_iftab_arp(ia)) && ife->ife_gotea) { + ea_set(eap, ife->ife_ea); + return TRUE; + } + return FALSE; +} + + +/* OSN_IPDEFAULT - Find a default IP interface entry; take the first one + * that's up and isn't a loopback. + */ +struct ifent * +osn_ipdefault(void) +{ + register int i = 0; + register struct ifent *ife = iftab; + + for (; i < iftab_nifs; ++i, ++ife) + if ((ife->ife_flags & IFF_UP) && !(ife->ife_flags & IFF_LOOPBACK)) + return ife; + return NULL; +} + +/* OSN_IFLOOKUP - Find interface entry in our table; NULL if none. + */ +struct ifent * +osn_iflookup(char *ifnam) +{ + register int i = 0; + register struct ifent *ife = iftab; + + for (; i < iftab_nifs; ++i, ++ife) + if (strcmp(ifnam, ife->ife_name) == 0) + return ife; + return NULL; +} + +/* OSN_IFEALOOKUP - Find ethernet address, barf if neither in our table nor + * available via OS. + */ +int +osn_ifealookup(char *ifnam, /* Interface name */ + unsigned char *eap) /* Where to write ether address */ +{ +#if NETIF_HAS_ETHLINK + register struct ifent *ife; + + if ((ife = osn_iflookup(ifnam)) + && (ife->ife_plink || ife->ife_gotea)) { + ea_set(eap, ife->ife_ea); + return TRUE; + } + return FALSE; +#else + return osn_ifeaget(-1, ifnam, eap, (unsigned char *)NULL); +#endif +} + +/* OSN_ARP_STUFF - stuff emulated-host ARP entry into kernel. +** Note it isn't necessary to specify an interface! +** Also, the code assumes that if an ARP entry already exists in the +** kernel for the given IP address, it will be reset to this new +** setting rather than (eg) failing. +*/ +int +osn_arp_stuff(unsigned char *ipa, unsigned char *eap, int pubf) +{ + char ipbuf[OSN_IPSTRSIZ]; + char eabuf[OSN_EASTRSIZ]; + +#if NETIF_HAS_ARPIOCTL + struct arpreq arq; + int sock; + + memset((char *)&arq, 0, sizeof(arq)); /* Clear & set up ARP req */ + arq.arp_pa.sa_family = AF_INET; /* Protocol addr is IP type */ + memcpy( /* Copy IP addr */ + (char *) &((struct sockaddr_in *)&arq.arp_pa)->sin_addr, + ipa, sizeof(struct in_addr)); + arq.arp_ha.sa_family = AF_UNSPEC; /* Hardware addr is Ether */ + ea_set(arq.arp_ha.sa_data, eap); /* Copy Ether addr */ + + /* Set ARP flags. Always make permanent so needn't keep checking. */ + arq.arp_flags = ATF_PERM; /* Make permanent */ + if (pubf) + arq.arp_flags |= ATF_PUBL; /* Publish it for us too! */ + + if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { /* Get random socket */ + syserr(errno, "Cannot set ARP entry for %s %s - socket()", + ip_adrsprint(ipbuf, ipa), + eth_adrsprint(eabuf, eap)); + return FALSE; + } + (void) ioctl(sock, SIOCDARP, (char *)&arq); /* Clear old info */ + if (ioctl(sock, SIOCSARP, (char *)&arq) < 0) { /* Set new */ + syserr(errno, "Cannot set ARP entry for %s %s - SIOCSARP", + ip_adrsprint(ipbuf, ipa), + eth_adrsprint(eabuf, eap)); + close(sock); + return FALSE; + } + close(sock); + return TRUE; +#elif CENV_SYS_XBSD + /* The new BSD systems completely did away with the ARP ioctls + and instead substituted a far more complicated PF_ROUTE socket hack. + Rather than attempt to duplicate the arp(8) utility code here, + let's try simply invoking it! + arp -S pub + */ + FILE *f; + int err; + char arpbuff[128]; + char resbuff[200]; + + sprintf(arpbuff, "/usr/sbin/arp -S %s %s %s", + ip_adrsprint(ipbuf, ipa), + eth_adrsprint(eabuf, eap), + (pubf ? "pub" : "")); + if (DP_DBGFLG) + dbprintln("invoking \"%s\"", arpbuff); + if ((f = popen(arpbuff, "r")) == NULL) { + syserr(errno, "cannot popen: %s", arpbuff); + error("Cannot set ARP entry for %s %s", + ip_adrsprint(ipbuf, ipa), + eth_adrsprint(eabuf, eap)); + return FALSE; + } + /* Read resulting output to avoid possibility it might hang otherwise */ + resbuff[0] = '\0'; + (void) fgets(resbuff, sizeof(resbuff)-1, f); + err = pclose(f); /* Hope this doesn't wait4() too long */ + if (err) { + dbprintln("arp exit error: status %d", err); + dbprintln("arp command was:", arpbuff); + } + if (DP_DBGFLG) + dbprintln("arp result \"%s\"", resbuff); + return TRUE; +#else + error("Cannot set ARP entry for %s %s - no implementation", + ip_adrsprint(ipbuf, ipa), + eth_adrsprint(eabuf, eap)); + return FALSE; +#endif +} + +/* OSN_IFIPGET - get IP address for a given interface name. + * This is separate from osn_ifiplook() in case iftab_init + * screws up for some ports. +*/ +int +osn_ifipget(int s, /* Socket for (AF_INET, SOCK_DGRAM, 0) */ + char *ifnam, /* Interface name */ + unsigned char *ipa) /* Where to write IP address */ +{ + int ownsock = FALSE; + char ipstr[OSN_IPSTRSIZ]; + struct ifreq ifr; + + if (s == -1) { + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + syserr(errno, "Can't get IP addr for \"%s\": socket()", ifnam); + return FALSE; + } + ownsock = TRUE; + } + + /* Find IP address for this interface */ + strncpy(ifr.ifr_name, ifnam, sizeof(ifr.ifr_name)); + if (ioctl(s, SIOCGIFADDR, (caddr_t *)&ifr) < 0) { + syserr(errno, "Can't get IP addr for \"%s\": SIOCGIFADDR", ifnam); + if (ownsock) + close(s); + return FALSE; + } + memcpy(ipa, + (char *)&(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr), + IP_ADRSIZ); + + if (ownsock) + close(s); + if (DP_DBGFLG) + dbprintln("IP address for \"%s\" = %s", + ifnam, ip_adrsprint(ipstr, ipa)); + return TRUE; +} + +/* OSN_IFNMGET - get IP netmask for a given interface name. + * Only needed for IMP, not NI20. +*/ +int +osn_ifnmget(int s, /* Socket for (AF_INET, SOCK_DGRAM, 0) */ + char *ifnam, /* Interface name */ + unsigned char *ipa) /* Where to write IP netmask */ +{ + int ownsock = FALSE; + char ipstr[OSN_IPSTRSIZ]; + struct ifreq ifr; + + if (s == -1) { + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + syserr(errno, "Can't get IP netmask for \"%s\": socket()", ifnam); + return FALSE; + } + ownsock = TRUE; + } + + /* Find IP address for this interface */ + strncpy(ifr.ifr_name, ifnam, sizeof(ifr.ifr_name)); + if (ioctl(s, SIOCGIFNETMASK, (caddr_t *)&ifr) < 0) { + syserr(errno, "Can't get IP netmask for \"%s\": SIOCGIFNETMASK",ifnam); + if (ownsock) + close(s); + return FALSE; + } + memcpy(ipa, + (char *)&(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr), + IP_ADRSIZ); + + if (ownsock) + close(s); + if (DP_DBGFLG) + dbprintln("IP netmask for \"%s\" = %s", + ifnam, ip_adrsprint(ipstr, ipa)); + return TRUE; +} + + +/* OSN_IFEAGET - get physical ethernet address for a given interface name. + * + * This is fairly tricky as the OSD mechanism for this tends to + * be *very* poorly documented. + * + * Apparently only DEC OSF/1 and Linux can find this directly. + * However, other systems seem to be divided into either of two + * camps: + * 4.3BSD: Hack - get the IP address of the interface, then use + * use SIOCGARP to look up the hardware address from the + * kernel's ARP table. Clever and gross. + * 4.4BSD: New regime, no ARP. But can get it directly as an + * AF_LINK family address in the ifconf table. +*/ +int +osn_ifeaget(int s, /* Socket for (AF_INET, SOCK_DGRAM, 0) */ + char *ifnam, /* Interface name */ + unsigned char *eap, /* Current ether address */ + unsigned char *def) /* Default (hardware) ether address */ +{ + char eastr[OSN_EASTRSIZ]; + +#if CENV_SYS_DECOSF /* Direct approach */ + { + int ownsock = FALSE; + struct ifdevea ifdev; + + if (s == -1) { + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + syserr(errno, "Can't get EN addr for \"%s\": socket()", ifnam); + return FALSE; + } + ownsock = TRUE; + } + strncpy(ifdev.ifr_name, ifnam, sizeof(ifdev.ifr_name)); + if (ioctl(s, SIOCRPHYSADDR, &ifdev) < 0) { + syserr(errno, "Can't get EN addr for \"%s\": SIOCRPHYSADDR", ifnam); + if (ownsock) close(s); + return FALSE; + } + if (ownsock) + close(s); + ea_set(eap, ifdev.current_pa); + if (def) + ea_set(def, ifdev.default_pa); + } + +#elif KLH10_NET_LNX + { + int ownsock = FALSE; + struct ifreq ifr; + + if (s == -1) { + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + syserr(errno, "Can't get EN addr for \"%s\": socket()", ifnam); + return FALSE; + } + ownsock = TRUE; + } + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifnam, sizeof(ifr.ifr_name)); + if (ioctl(s, SIOCGIFHWADDR, &ifr) < 0 ) { + syserr(errno, "SIOCGIFHWADDR of %s failed", ifnam); + if (ownsock) close(s); + return FALSE; + } + + if (ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER) { + error("%s is not an ethernet - ARPHRD type %d", + ifnam, ifr.ifr_hwaddr.sa_family); + if (ownsock) close(s); + return FALSE; + } + if (ownsock) + close(s); + ea_set(eap, &ifr.ifr_addr.sa_data[0]); /* Return the ether address */ + if (def) + memset(def, 0, ETHER_ADRSIZ); + } + +#elif NETIF_HAS_ARPIOCTL + { + /* Much more general hack */ + unsigned char ipchr[IP_ADRSIZ]; + char ipstr[OSN_IPSTRSIZ]; + + /* Get IP address for this interface, as an argument for ARP lookup */ + if (!osn_ifipget(s, ifnam, ipchr)) { + error("Can't get EN addr for \"%s\": osn_ifipget failed", ifnam); + return FALSE; + } + + /* Have IP address, now do ARP lookup hackery */ + if (!osn_arp_look((struct in_addr *)ipchr, eap)) { + syserr(errno,"Can't find EN addr for \"%s\" %s using ARP", + ifnam, ip_adrsprint(ipstr, ipchr)); + return FALSE; + } + if (def) + memset(def, 0, ETHER_ADRSIZ); + } + +#elif NETIF_HAS_ETHLINK + /* Should never happen unless osn_ifeaget() is called directly + without going through osn_ifealookup(). If so, attempt + to do right thing anyway. + */ + { + register struct ifent *ife; + + if (iftab_initf == 0) { + /* Try to initialize iftab, ignore errors */ + (void) osn_iftab_init(IFTAB_ETHS); + } + if (!(ife = osn_iflookup(ifnam)) + || !(ife->ife_gotea)) { + error("No EN addr in iftab for \"%s\"", ifnam); + return FALSE; + } + ea_set(eap, ife->ife_ea); + if (def) + memset(def, 0, ETHER_ADRSIZ); + } + +#else +# error "Unimplemented OS routine osn_ifeaget()" +#endif + dbprintln("EN addr for \"%s\" = %s", + ifnam, eth_adrsprint(eastr, eap)); + return TRUE; +} + +/* OSN_PFEAGET - get physical ethernet address for an open packetfilter FD. + * + * Also not well documented, but generally easier to perform. +*/ +int +osn_pfeaget(int pfs, /* Packetfilter socket or FD */ + char *ifnam, /* Interface name (sometimes needed) */ + unsigned char *eap) /* Where to write ether address */ +{ + +#if KLH10_NET_NIT + /* SunOS/Solaris: The EA apparently can't be found until after the PF FD + ** is open; an attempt to simply do a socket(AF_UNSPEC, SOCK_DGRAM, 0) + ** will just fail with a protocol-not-supported error. And using an + ** AF_INET socket just gets us the IP address, sigh. + ** Perhaps using AF_DECnet would work, as it does for OSF/1? + */ + struct ifreq ifr; + struct strioctl si; /* Must use kludgy indirect cuz stream */ + + strncpy(ifr.ifr_name, ifnam, sizeof(ifr.ifr_name)); + si.ic_timout = INFTIM; + si.ic_cmd = SIOCGIFADDR; + si.ic_len = sizeof ifr; + si.ic_dp = (char *)𝔦 + if (ioctl(pfs, I_STR, (char *)&si) < 0) { + syserr(errno, "ether SIOCGIFADDR failed for \"%s\"", ifnam); + ea_clr(eap); + return FALSE; + } + ea_set(eap, &ifr.ifr_addr.sa_data[0]); /* Return the ether address */ + +#elif KLH10_NET_PFLT /* Really DECOSF */ + struct endevp endp; + + if (ioctl(pfs, EIOCDEVP, (caddr_t *)&endp) < 0) { + syserr(errno, "EIOCDEVP failed"); + ea_clr(eap); + return FALSE; + } + if (endp.end_dev_type != ENDT_10MB + || endp.end_addr_len != ETHER_ADRSIZ) { + syserr(errno, "EIOCDEVP returned non-Ethernet info!"); + ea_clr(eap); + return FALSE; + } + ea_set(eap, endp.end_addr); + +#elif KLH10_NET_BPF + struct ifreq ifr; + + strncpy(ifr.ifr_name, ifnam, sizeof(ifr.ifr_name)); + if (ioctl(pfs, SIOCGIFADDR, (char *) &ifr) < 0) { + syserr(errno, "SIOCGIFADDR for EA failed (%d, \"%s\")", + pfs, ifnam); + ea_clr(eap); + return FALSE; + } + ea_set(eap, &ifr.ifr_addr.sa_data[0]); /* Return the ether address */ + +#elif KLH10_NET_DLPI + { + static int dleaget(int fd, unsigned char *eap); + + /* Use handy auxiliary to hide hideous DLPI details */ + if (!dleaget(pfs, eap)) + return FALSE; + } +#else + if (!osn_ifeaget(pfs, ifnam, eap, (unsigned char *)NULL)) + return FALSE; +#endif + + if (DP_DBGFLG) { + char eastr[OSN_EASTRSIZ]; + + dbprintln("EN addr for \"%s\" = %s", + ifnam, eth_adrsprint(eastr, eap)); + } + return TRUE; +} + +#if CENV_SYS_XBSD +static int osn_pareth(char *cp, unsigned char *adr); +#endif + +/* OSN_ARP_LOOK - Attempt looking up ARP information from OS + * This may be needed by DPIMP to find the ether addr for an IP address, + * or by DPNI20 when trying to determine its own ether address. + * In any case, it's a very OS-dependent technique worth encapsulating. + * + * NOTE: Failure to find an address should not cause an error printout, + * unless there is some actual system glitch. + */ +int +osn_arp_look(struct in_addr *ipa, /* Look up this IP address */ + unsigned char *eap) /* Return EA here */ +{ +#if NETIF_HAS_ARPIOCTL + unsigned char ipchr[IP_ADRSIZ]; + char ipstr[OSN_IPSTRSIZ]; + struct arpreq arq; + int sock; + + /* Query kernel directly. */ + memset((char *)&arq, 0, sizeof(arq)); /* Clear & set up ARP req */ + arq.arp_pa.sa_family = AF_INET; /* Protocol addr is IP type */ + arq.arp_ha.sa_family = AF_UNSPEC; /* Hardware addr is Ether */ + ((struct sockaddr_in *)&arq.arp_pa)->sin_addr = *ipa; + + if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { /* Get random socket */ + syserr(errno, "osn_arp_look socket()"); + return FALSE; + } + if (ioctl(sock, SIOCGARP, (char *)&arq) != 0) { /* Get ARP info */ + close(sock); + /* If SIOCGARP fails, assume it just means the entry wasn't found, + rather than implying some error. + */ + if (DP_DBGFLG) + dbprintln("No ARP info for %s", ip_adrsprint(ipstr, ipchr)); + return FALSE; + } + close(sock); + + /* Won, check flags */ + if (!(arq.arp_flags & ATF_COM)) { + if (DP_DBGFLG) + dbprintln("ARP entry incomplete for %s", + ip_adrsprint(ipstr, ipchr)); + return FALSE; + } + ea_set((char *)eap, arq.arp_ha.sa_data); /* Copy ether addr */ + return TRUE; + +#elif CENV_SYS_XBSD + /* The new BSD stuff did away with the ARP ioctls and substituted an + ** extremely complicated routing IPC mechanism. For the time being + ** I'll just use a horrible hack. + */ + unsigned char *cp = (unsigned char *)ipa; + char *arppath = "/usr/sbin/arp"; + char arpbuff[128]; + FILE *f; + char fhost[100]; + char fip[32]; + char fat[8]; + char fhex[32]; + struct eth_addr etha; + int res; + + /* Use -n to avoid symbolic lookup hangs */ + sprintf(arpbuff, "%s -n %u.%u.%u.%u", arppath, + cp[0], cp[1], cp[2], cp[3]); + if (DP_DBGFLG) + dbprintln("invoking \"%s\"", arpbuff); + if ((f = popen(arpbuff, "r")) == NULL) { + syserr(errno, "cannot popen: %s", arpbuff); + return FALSE; + } + arpbuff[0] = '\0'; + (void) fgets(arpbuff, sizeof(arpbuff)-1, f); + res = pclose(f); /* Hope this doesn't wait4() too long */ + if (res) + dbprintln("arp exit error: status %d", res); + if (DP_DBGFLG) + dbprintln("arp result \"%s\"", arpbuff); + + /* Parse the result. There are three possible formats: + ** () at 4+ words + ** () at (incomplete) 4 words only? + ** () -- no entry 5 words only + */ + res = sscanf(arpbuff, "%99s %31s %7s %31s", fhost, fip, fat, fhex); + if (res == 4 && (strcmp(fat, "at")==0) + && osn_pareth(fhex, (unsigned char *)ða)) { + ea_set(eap, ða); + return TRUE; + } + + /* Failed, see if failure is understood or not */ + if (res == 4 + && ( ((strcmp(fat, "at")==0) && strcmp(fhex, "(incomplete)")==0) + || ((strcmp(fat, "--")==0) && strcmp(fhex, "no")==0))) { + + /* Failed in a way we understand, so don't complain */ + return FALSE; + } + error("osn_arp_look result unparseable: \"%s\"", arpbuff); + return FALSE; + +#else + error("osn_arp_look not implemented"); + return FALSE; +#endif +} + +#if CENV_SYS_XBSD /* Auxiliary to support code in osn_arp_look() */ +static int +osn_pareth(char *cp, unsigned char *adr) +{ + unsigned int b1, b2, b3, b4, b5, b6; + int cnt; + + cnt = sscanf(cp, "%x:%x:%x:%x:%x:%x", &b1, &b2, &b3, &b4, &b5, &b6); + if (cnt != 6) { + /* Later try as single large address #? */ + return FALSE; + } + if (b1 > 255 || b2 > 255 || b3 > 255 || b4 > 255 || b5 > 255 || b6 > 255) + return FALSE; + *adr++ = b1; + *adr++ = b2; + *adr++ = b3; + *adr++ = b4; + *adr++ = b5; + *adr = b6; + return TRUE; +} +#endif /* CENV_SYS_XBSD */ + +#if !OSN_USE_IPONLY /* This stuff not needed for DPIMP */ + +/* OSN_IFEASET - Set physical ethernet address for a given interface name. + * + * Like osn_ifeaget, also tricky with obscure non-standard mechanisms. + * +*/ +int +osn_ifeaset(int s, /* Socket for (AF_INET, SOCK_DGRAM, 0) */ + char *ifnam, /* Interface name */ + unsigned char *newpa) /* New ether address */ +{ +#if CENV_SYS_DECOSF || KLH10_NET_LNX || CENV_SYS_FREEBSD + + /* Common preamble code */ + int ownsock = FALSE; + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifnam, sizeof(ifr.ifr_name)); + if (s == -1) { + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + syserr(errno, "Failed osn_ifeaset socket()"); + return FALSE; + } + ownsock = TRUE; + } + +# if CENV_SYS_DECOSF /* Direct approach */ + /* NOTE!!! DECOSF Doc bug! + ** Contrary to appearances in osnpf_ifnam && osnpf->osnpf_ifnam[0]) { /* Allow default ifc */ + /* Bind to specified HW interface */ + if (ioctl(fd, EIOCSETIF, osnpf->osnpf_ifnam) < 0) + esfatal(1, "Couldn't open packetfilter for \"%s\" - EIOCSETIF", + osnpf->osnpf_ifnam); + } + + /* Set up various mode & flag stuff */ + + /* Only the super-user can allow promiscuous or copy-all to be set. */ +#if 0 + /* Allow promiscuous mode if someone wants to use it. + ** For shared interface, never true. + */ + arg = 1; + if (ioctl(fd, EIOCALLOWPROMISC, &arg)) + esfatal(1, "EIOCALLOWPROMISC failed"); /* Maybe not SU? */ +#endif + /* Allow ENCOPYALL bit to be set. + ** Always use this for shared interface so the multicast-bit stuff + ** we grab can also be seen by kernel and other sniffers. + */ + arg = 1; + if (ioctl(fd, EIOCALLOWCOPYALL, &arg)) + esfatal(1, "EIOCALLOWCOPYALL failed"); /* Maybe not SU? */ + + + /* Ensure following bits clear: + ** no timestamps, no batching, no held signal + */ + bits = (ENTSTAMP | ENBATCH | ENHOLDSIG); + if (ioctl(fd, EIOCMBIC, &bits)) + esfatal(1, "EIOCMBIC failed"); + + /* Set: + ** ??? Put interface into promisc mode (if allowed by SU) + ** KLH: No longer -- shouldn't be necessary. + ** Pass packet along to other filters + ** Copy packets to/from native kernel ptcls - to allow talking with + ** native host platform! + */ + bits = (/* ENPROMISC | */ ENNONEXCL | ENCOPYALL); + if (ioctl(fd, EIOCMBIS, &bits)) + esfatal(1, "ioctl: EIOCMBIS"); + + /* Set up packet filter for it - only needed if sharing interface */ + if (!osnpf->osnpf_dedic) { + if (ioctl(fd, EIOCSETF, pfbuild(pfarg, &(osnpf->osnpf_ip.ia_addr))) < 0) + esfatal(1, "EIOCSETF failed"); + } + + /* Now can get our interface's ethernet address. + ** In general, this has to wait until after the packetfilter is opened, + ** since until then we don't have a handle on the specific interface + ** that will be used. + */ + { + struct endevp endp; + + if (ioctl(fd, EIOCDEVP, (caddr_t *)&endp) < 0) + esfatal(1, "EIOCDEVP failed"); + if (endp.end_dev_type != ENDT_10MB + || endp.end_addr_len != 6) + esfatal(1, "EIOCDEVP returned non-Ethernet info!"); + ea_set(&(osnpf->osnpf_ea), endp.end_addr); + } + + /* Miscellaneous stuff */ + + /* Hack: use timeout mechanism to see if it helps avoid wedging system + ** when using OSF/1 V3.0. + */ + if (osnpf->osnpf_rdtmo) + { + struct timeval tv; + + tv.tv_sec = osnpf->osnpf_rdtmo; + tv.tv_usec = 0; + + if (ioctl(fd, EIOCSRTIMEOUT, &tv) < 0) + esfatal(1, "EIOCSRTIMEOUT failed"); + } + + /* If backlog param was provided, try to set system's idea of how many + ** input packets can be kept on kernel queue while waiting for us to + ** read them into user space. + */ + if (osnpf->osnpf_backlog) { + if (ioctl(fd, EIOCSETW, &(osnpf->osnpf_backlog)) < 0) + esfatal(1, "EIOCSETW failed"); + } + + /* Ready to roll! */ + return fd; +} +#endif /* KLH10_NET_PFLT */ + +#if KLH10_NET_BPF + +int +osn_pfinit(register struct osnpf *osnpf, void *arg) +{ + int fd; + struct bpf_version bv; + struct ifreq ifr; + u_int u; + int i; + + /* No "default interface" concept here */ + if (!osnpf->osnpf_ifnam || !osnpf->osnpf_ifnam[0]) + esfatal(1, "Packetfilter interface must be specified"); + + /* Open an unused BPF device for R/W */ + fd = pfopen(); /* Will abort if fail */ + + /* Check the filter language version number */ + if (ioctl(fd, BIOCVERSION, (char *) &bv) < 0) + esfatal(1, "kernel BPF interpreter out of date"); + else if (bv.bv_major != BPF_MAJOR_VERSION || + bv.bv_minor < BPF_MINOR_VERSION) + efatal(1, "requires BPF language %d.%d or higher; kernel is %d.%d", + BPF_MAJOR_VERSION, BPF_MINOR_VERSION, bv.bv_major, bv.bv_minor); + + /* Set immediate mode so that packets are processed as they arrive, + rather than waiting until timeout or buffer full. + */ + i = 1; + if (ioctl(fd, BIOCIMMEDIATE, (char *) &i) < 0) + esfatal(1, "BIOCIMMEDIATE failed"); + + /* Set the read() buffer size. + Must be set before interface is attached! + */ + u = OSN_BPF_MTU; + if (ioctl(fd, BIOCSBLEN, (char *) &u) < 0) + esfatal(1, "BIOCSBLEN failed"); + + /* Set up packet filter for it - only needed if sharing interface? + Not sure whether it's needed for a dedicated interface; will need + to experiment. + */ + if (!osnpf->osnpf_dedic) { + struct OSN_PFSTRUCT *pf; + + /* Set the kernel packet filter */ + pf = pfbuild(arg, &(osnpf->osnpf_ip.ia_addr)); + if (ioctl(fd, BIOCSETF, (char *)pf) < 0) + esfatal(1, "BIOCSETF failed"); + } + + + /* Set read timeout. + Safety check in order to avoid infinite hangs if something + wedges up. The periodic re-check overhead is insignificant. + */ + /* Set read timeout. + Safety hack derived from need to use timeout to avoid wedging system + when using OSF/1 V3.0. Probably useful for other systems too. + */ + if (osnpf->osnpf_rdtmo) + { + struct timeval tv; + + tv.tv_sec = osnpf->osnpf_rdtmo; + tv.tv_usec = 0; + + if (ioctl(fd, BIOCSRTIMEOUT, (char *) &tv) < 0) + esfatal(1, "BIOCSRTIMEOUT failed"); + } + + /* Attach/bind to desired interface device + */ + strncpy(ifr.ifr_name, osnpf->osnpf_ifnam, sizeof(ifr.ifr_name)); + if (ioctl(fd, BIOCSETIF, (char *) &ifr) < 0) + esfatal(1, "BIOCSETIF failed"); + + /* This code only works with Ethernet, so check for that. + Note cannot check until after interface is attached. + */ + if (ioctl(fd, BIOCGDLT, (char *) &u) < 0) + esfatal(1, "BIOCGDLT failed"); + if (u != DLT_EN10MB) + efatal(1, "%s is not an ethernet", osnpf->osnpf_ifnam); + + + /* Now get our interface's ethernet address. + ** In general, this has to wait until after the packetfilter is opened, + ** since until then we don't have a handle on the specific interface + ** that will be used. + */ + (void) osn_pfeaget(fd, osnpf->osnpf_ifnam, + (unsigned char *)&(osnpf->osnpf_ea)); + + /* Ready to roll! */ + return fd; +} + +#endif /* KLH10_NET_BPF */ + +#if KLH10_NET_TUN +/* + In order to use the TUN interface we have to do the equivalent of + (1) "ifconfig tun0 up" + as well as + (2) "arp perm pub" + and finally + (3) "sysctl -w net.inet.ip.forwarding=1" + if you want to connect in from other machines besides + the one the emulator is running on. This last step must + still be done externally as it affects overall system + security. + +For (1) the code must flush any existing address, add the new address, and +ensure the interface's IFF_UP flag is set. Assuming we'll always be running +on *BSD because that's the only system supporting TUN at the moment, the +necessary calls are + SIOCDIFADDR - Delete existing address + SIOCAIFADDR - Add existing address (and broadcast and mask) + SIOCSIFFLAGS - To set IFF_UP + + Some comments about how SIOCAIFADDR and SIOCDIFADDR work, based on + observation of the FreeBSD code: + + The miniscule doc is in netintro(4). Use an AF_INET, DGRAM socket. + Both take: + +DELETE takes a struct ifreq (can use an ifaliasreq like the doc claims, +but the SIOCDIFADDR ioctl only defines it as taking an ifreq's worth of +data!). If the first addr matches an existing one, that one is +deleted. Otherwise, the first AF_INET address is deleted; INADDR_ANY +from in.h (ie all zeros) is probably the right thing for this case. + + +ADD takes a new struct so as to set everything at once: + + struct ifaliasreq { + char ifra_name[IFNAMSIZ]; // if name, e.g. "en0" + struct sockaddr ifra_addr; + struct sockaddr ifra_broadaddr; + struct sockaddr ifra_mask; + }; + +For ADD, the ifra_addr field is the new address to add. +However, the ifra_broadaddr field is actually used in two different ways. + If the interface has IFF_BROADCAST set, then ifra_broadaddr is the + broadcast address, as advertised. + But if it has IFF_POINTOPOINT set, it is interpreted as "ifra_dstaddr"; + the destination (or remote) address. It is an error if this address + is INADDR_ANY. +The ifra_mask field is ignored (left as-is or zeroed if new) unless + ifra_mask.sin_len is non-zero. + + */ + +int +osn_pfinit(register struct osnpf *osnpf, void *arg) +{ + int allowextern = TRUE; /* For now, always try for external access */ + int fd; + char tunname[sizeof "/dev/tun000"]; + char *ifnam = tunname + sizeof("/dev/")-1; + int i = -1; + char ipb1[OSN_IPSTRSIZ]; + char ipb2[OSN_IPSTRSIZ]; + struct ifent *ife = NULL; /* Native host's default IP interface if one */ + struct in_addr iplocal; /* TUN ifc address at hardware OS end */ + struct in_addr ipremote; /* Address at remote (emulated host) end */ + static unsigned char ipremset[4] = { 192, 168, 0, 44}; + + /* Remote address is always that of emulated machine */ + ipremote = osnpf->osnpf_ip.ia_addr; + + /* Local address is that of hardware machine if we want to permit + external access. If not, it doesn't matter (and may not even + exist, if there is no hardware interface) + */ + if (allowextern) { + if (osn_iftab_init(IFTAB_IPS) && (ife = osn_ipdefault())) { + iplocal = ife->ife_ipia; + } else { + error("Cannot find default IP interface for host"); + allowextern = FALSE; + } + } + if (!allowextern) { + /* Make up bogus IP address for internal use */ + memcpy((char *)&iplocal, ipremset, 4); + } + + if (DP_DBGFLG) + dbprint("Opening TUN device"); + + do { + sprintf(tunname, "/dev/tun%d", ++i); + } while ((fd = open(tunname, O_RDWR)) < 0 && errno == EBUSY); + + if (fd < 0) + esfatal(1, "Couldn't open tunnel device %s", tunname); + + if (DP_DBGFLG) + dbprintln("Opened %s, configuring for local %s, remote %s", + ifnam, + ip_adrsprint(ipb1, (unsigned char *)&iplocal), + ip_adrsprint(ipb2, (unsigned char *)&ipremote)); + + + /* Activate TUN device. + First address is "local" -- doesn't matter if all we care about is + talking to the native/hardware host, and it can be set to any of the IP + address ranges reserved for LAN-only (non-Internet) use, such as + 10.0.0.44. + However, if planning to allow other machines to access the virtual + host, probably best to use an address suitable for the same LAN + subnet as the hardware host. + Unclear yet whether it works to use the host's own address; it at + least allows the configuration to happen. + + Second address is "remote" -- the one the emulated host is using. + It should probably match the same network as the local address, + especially if planning to connect from other machines. + */ +#if 0 /* Hacky method */ + { + char cmdbuff[128]; + int res; + sprintf(cmdbuff, "ifconfig %s %s %s up", + ifnam, + ip_adrsprint(ipb1, (unsigned char *)&iplocal), + ip_adrsprint(ipb2, (unsigned char *)&ipremote)); + + if ((res = system(cmdbuff)) != 0) { + esfatal(1, "osn_pfinit: ifconfig failed to initialize tunnel device?"); + } + } +#else + { + /* Internal method */ + int s; + struct ifaliasreq ifra; + struct ifreq ifr; + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + esfatal(1, "pf_init: tun socket() failed"); + } + + /* Delete first (only) IP address for this device, if any. + Ignore errors. + */ + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifnam, sizeof(ifr.ifr_name)); + if (ioctl(s, SIOCDIFADDR, &ifr) < 0) { + if (DP_DBGFLG) + syserr(errno, "osn_pfinit tun SIOCDIFADDR failed"); + } + + memset(&ifra, 0, sizeof(ifra)); + strncpy(ifra.ifra_name, ifnam, sizeof(ifra.ifra_name)); + ((struct sockaddr_in *)(&ifra.ifra_addr))->sin_len = sizeof(struct sockaddr_in); + ((struct sockaddr_in *)(&ifra.ifra_addr))->sin_family = AF_INET; + ((struct sockaddr_in *)(&ifra.ifra_addr))->sin_addr = iplocal; + ((struct sockaddr_in *)(&ifra.ifra_broadaddr))->sin_len = sizeof(struct sockaddr_in); + ((struct sockaddr_in *)(&ifra.ifra_broadaddr))->sin_family = AF_INET; + ((struct sockaddr_in *)(&ifra.ifra_broadaddr))->sin_addr = ipremote; + if (ioctl(s, SIOCAIFADDR, &ifra) < 0) { + esfatal(1, "osn_pfinit tun SIOCAIFADDR failed"); + } + + /* Finally, turn on IFF_UP just in case the above didn't do it. + Note interface name is still there from the SIOCDIFADDR. + */ + if (ioctl(s, SIOCGIFFLAGS, &ifr) < 0) { + esfatal(1, "osn_pfinit tun SIOCGIFFLAGS failed"); + } + if (!(ifr.ifr_flags & IFF_UP)) { + ifr.ifr_flags |= IFF_UP; + if (ioctl(s, SIOCSIFFLAGS, &ifr) < 0) { + esfatal(1, "osn_pfinit tun SIOCSIFFLAGS failed"); + } + if (DP_DBGFLG) + dbprint("osn_pfinit tun did SIOCSIFFLAGS"); + } + } +#endif + + /* Now optionally determine ethernet address. + This amounts to what if anything we should put in the native + host's ARP tables. + - If we only intend to use the net between the virtual host and + its hardware host, then no ARP hackery is needed. + - However, if the intent is to allow traffic between the virtual + host and other machines on the LAN or Internet, then an ARP + entry is required. It must advertise the virtual host's IP + address, using one of the hardware host's ethernet addresses + so any packets on the LAN for the virtual host will at least + wind up arriving at the hardware host it's running on. + */ + + /* Simple method is to get the ifconf table and scan it to find the + first interface with both an IP and ethernet address given. + Non-4.4BSD systems may not provide the latter datum, but if + we're using TUN then we almost certainly also have the good stuff + in ifconf. + */ + if (allowextern && ife) { + /* Need to determine ether addr of our default interface, then + publish an ARP entry mapping the virtual host to the same + ether addr. + */ + (void) osn_arp_stuff((unsigned char *)&ipremote, ife->ife_ea, TRUE); + + /* Return that as our ether address */ + ea_set((char *)&osnpf->osnpf_ea, ife->ife_ea); + } else { + /* Assume no useful ether addr */ + ea_clr((char *)&osnpf->osnpf_ea); + } + + if (DP_DBGFLG) + dbprintln("osn_pfinit tun completed"); + + return fd; +} + +#endif /* KLH10_NET_TUN */ + +#if KLH10_NET_LNX + +/* + The Linux PF_PACKET interface is described to some extent + by the packet(7) man page. + + Linux provides no kernel packet filtering mechanism other than + possibly a check on the ethernet protocol type, but this is useless + for us since we'll always want to check for more than just one type; + e.g. IP and ARP, plus possibly 802.3 or DECNET packets. + + From the man page for packet(7): + By default all packets of the specified protocol type are + passed to a packet socket. To only get packets from a spe- + cific interface use bind(2) specifying an address in a + struct sockaddr_ll to bind the packet socket to an inter- + face. Only the sll_protocol and the sll_ifindex address + fields are used for purposes of binding. + */ +int +osn_pfinit(struct osnpf *osnpf, void *arg) +{ + int fd; + char *ifcname = osnpf->osnpf_ifnam; + struct ifreq ifr; + + /* Open a socket of the desired type. + */ + struct sockaddr_ll sll; + int ifx; + + /* Get raw packets with ethernet headers + */ + fd = socket(PF_PACKET, SOCK_RAW, +#if 0 /*OSN_USE_IPONLY*/ /* If DPIMP or otherwise IP only */ + htons(ETH_P_IP) /* for IP only */ +#else + htons(ETH_P_ALL) /* for everything */ +#endif + ); + if (fd < 0) + esfatal(1, "Couldn't open packet socket"); + + /* Need ifc index in order to do binding, so get it. */ + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifcname, sizeof(ifr.ifr_name)); + if (ioctl(fd, SIOCGIFINDEX, &ifr) < 0 ) + esfatal(1, "SIOCGIFINDEX of %s failed", ifcname); + ifx = ifr.ifr_ifindex; + + /* Bind to proper device/interface using ifc index */ + memset(&sll, 0, sizeof(sll)); + sll.sll_family = AF_PACKET; + sll.sll_protocol = htons(ETH_P_ALL); + sll.sll_ifindex = ifx; + if (bind(fd, (struct sockaddr *)&sll, sizeof(sll))) + esfatal(1, "bind to %s failed", ifcname); + + /* This code only works with Ethernet, so check for that */ + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifcname, sizeof(ifr.ifr_name)); + if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0 ) + esfatal(1, "SIOCGIFHWADDR of %s failed", ifcname); + + if (ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER) + efatal(1, "%s is not an ethernet - ARPHRD type %d", + ifcname, ifr.ifr_hwaddr.sa_family); + + /* Finally, attempt to determine current ethernet MAC address. + Assume above call returned it in sa_data. + */ + ea_set(&osnpf->osnpf_ea, &ifr.ifr_addr.sa_data[0]); + + return fd; +} + +#endif /* KLH10_NET_LNX */ + +#if KLH10_NET_NIT + +/* NIT packetfilter initialization */ + +int +osn_pfinit(struct osnpf *osnpf, void *arg) +{ + int fd; + struct strioctl si; + struct ifreq ifr; + + /* Open NIT stream. We'll be doing both R and W. */ + if ((fd = open("/dev/nit", O_RDWR)) < 0) + esfatal(1, "Couldn't open NIT"); + + /* Ensure that each read gives us one packet */ + if (ioctl(fd, I_SRDOPT, (char *)RMSGD) < 0) + esfatal(1, "I_SRDOPT failed"); + + /* Set up kernel filtering */ + if (ioctl(fd, I_PUSH, "pf") < 0) + esfatal("I_PUSH pf failed"); + + /* Set up packet filter for it */ + if (!osnpf->osnpf_dedic) { + struct OSN_PFSTRUCT *pf; + pf = pfbuild(arg, &osnpf->osnpf_ip.ia_addr); + si.ic_timout = INFTIM; /* Should this be osnpf_rdtmo? */ + si.ic_cmd = NIOCSETF; /* Set packet filter */ + si.ic_len = sizeof(*pf); /* XXX Unfortunate dependency */ + si.ic_dp = (char *)pf; + if (ioctl(fd, I_STR, (char *)&si) < 0) + esfatal(1, "NIOCSETF failed"); + } + + /* Finally, bind to proper device and flush anything accumulated */ + strncpy(ifr.ifr_name, osnpf->osnpf_ifnam, sizeof(ifr.ifr_name)); + si.ic_cmd = NIOCBIND; + si.ic_len = sizeof(ifr); + si.ic_dp = (char *)𝔦 + if (ioctl(fd, I_STR, (char *)&si) < 0) + esfatal(1, "NIOCBIND failed"); + + if (ioctl(fd, I_FLUSH, (char *)FLUSHR) < 0) + esfatal(1, "I_FLUSH failed"); + + /* Get our ethernet address. + ** This can't be done until after the NIT is open and bound. + */ + (void) osn_pfeaget(fd, osnpf->osnpf_ifnam, &(osnpf->osnpf_ea)); + + /* Ready to roll! */ + return fd; +} +#endif /* KLH10_NET_NIT */ + +#if KLH10_NET_DLPI + +/* DLPI packetfilter initialization */ + +#define MAXSIZE (32 * 1024) /* Whuffo? */ +#define MAXADDR 1024 +#define MAXCTLBUF (sizeof (dl_unitdata_req_t) + sizeof (long) + MAXADDR) +#define MAXDLBUF 8192 /* Maybe just MAXCTLBUF */ + +struct ledladdr { + struct ether_addr dl_phys; + unsigned short dl_sap; +}; + +struct dlpictx { + int dc_fd; + struct strbuf dc_ctl; + char dc_ebuf[200]; /* Error output */ + long dc_buf[MAXDLBUF]; +}; + +/* Fake SAP must be above 1500 to avoid 802.3 interpretation, but +** might be dangerous if it were 2048 which is IP (try that later, tho) +*/ +#define FAKESAP 2049 + +#if OSN_USE_IPONLY +static int dlfastpathon(struct dlpictx *dc, int sap); +#endif + +static int strioctl(int fd, int cmd, int len, char *dp); +static int dl_sendreq(struct dlpictx *dc, char *ptr, int len, char *what); +static int strgetmsg(struct dlpictx *dc, char *caller, struct strbuf *datap, + int *flagsp); +static int dlattachreq(struct dlpictx *dc, long ppa); +static int dlokack(struct dlpictx *dc); +static int dlbindreq(struct dlpictx *dc, u_long sap, u_long max_conind, + unsigned short service_mode, unsigned short conn_mgmt); +static int dlbindack(struct dlpictx *dc); +static int dlpromisconreq(struct dlpictx *dc, u_long level); +static int dladdrreq(struct dlpictx *dc, int type); +static int dladdrack(struct dlpictx *dc, unsigned char *addr); +static int dlsetaddrreq(struct dlpictx *dc, unsigned char *addr); +#if 0 +static int dlpromiscoff(struct dlpictx *dc); +static int dlinforeq(struct dlpictx *dc); +static int dlinfoack(struct dlpictx *dc); +static int dlenabmulti(struct dlpictx *dc, char *addrp, int len); +static int dldisabmulti(struct dlpictx *dc, char *addrp, int len); +static int dldetachreq(struct dlpictx *dc); +static int dlunbindreq(struct dlpictx *dc); +#endif + + +int osn_pfinit(struct osnpf *osnpf, void *arg) +{ + int fd; + int ppa; + char *cp; + unsigned char curea[ETHER_ADRSIZ]; + char devpath[IFNAMSIZ+40]; + char eastr[OSN_EASTRSIZ]; + unsigned char *ucp; + struct dlpictx dc; + + /* Figure out right name of device to use. + ** Munch device spec, eg "hmeNN" becomes /dev/hme, unit NN. + ** Code formerly allowed full pathname as device spec, but no longer. + */ + if (osnpf->osnpf_ifnam[0] == 0) + efatal(1, "Ethernet DLPI interface must be specified"); + strcpy(devpath, "/dev/"); + strcat(devpath, osnpf->osnpf_ifnam); /* Buffer always big enough */ + ppa = 0; /* Assume unit #0 */ + cp = &devpath[strlen(devpath)-1]; /* Point to end of string */ + if (isdigit(*cp)) { /* Go to start of unit digits */ + while (isdigit(*--cp)); /* (code safe cuz of /dev/ prefix) */ + ppa = atoi(++cp); + *cp = '\0'; /* Chop off unit digits */ + } + + /* Open device. We'll be doing both R and W. */ + if ((fd = open(devpath, O_RDWR)) < 0) { + esfatal(1, "Couldn't open DLPI packetfilter for ifc \"%s\" (%s)", + devpath, osnpf->osnpf_ifnam); + } + memset((void *)&dc, 0, sizeof(dc)); + dc.dc_fd = fd; + + /* Attach to specific unit */ + if (dlattachreq(&dc, (long)ppa) + || dlokack(&dc)) + efatal(1, dc.dc_ebuf); + + /* Bind */ +#if OSN_USE_IPONLY /* Note using IP SAP */ + if (dlbindreq(&dc, 0x800, 0, DL_CLDLS, 0) +#else /* Note using fake SAP to avoid Solaris lossage */ + if (dlbindreq(&dc, FAKESAP, 0, DL_CLDLS, 0) +#endif + || dlbindack(&dc)) + efatal(1, dc.dc_ebuf); + +#if OSN_USE_IPONLY /* Apparently only needed for this */ + /* Do stuff for "fastpath" which may be needed to allow header to + be included in data buffer rather than separate control struct. + */ + if (dlfastpathon(&dc, 0) < 0) + efatal(1, dc.dc_ebuf); +#elif 0 /* !OSN_USE_IPONLY */ /* Apparently not needed */ + if (dlfastpathon(&dc, FAKESAP) < 0) + efatal(1, dc.dc_ebuf); +#endif + + /* Set up various mode & flag stuff */ + + /* Do input side of DLPI stream */ + +#if CENV_SYS_SOLARIS + /* Turn on raw receive path, so that ethernet header is included in data. + ** This is a special Solaris frob. + */ + if (strioctl(fd, DLIOCRAW, 0, NULL) < 0) { + esfatal(1, "DLIOCRAW ioctl"); + } +#endif + +#if !OSN_USE_IPONLY + /* Don't need this hackery if only want IP packets */ + /* Enable receiving all packets, all SAPs (ethernet header type values) */ + +# if CENV_SYS_SOLARIS + /* Enable promiscuous mode. On Solaris, this is required in order + ** for ALLSAP to work!!! Gross bletcherous misfeatured bug!!! + */ + if ((dlpromisconreq(&dc, DL_PROMISC_PHYS) < 0) + || dlokack(&dc) < 0) + efatal(1, dc.dc_ebuf); +# endif + /* Evidently must explicitly ask for promiscuous SAPs */ + if (dlpromisconreq(&dc, DL_PROMISC_SAP) < 0 + || dlokack(&dc) < 0) + efatal(1, dc.dc_ebuf); + /* And multicast too!? To quote tcpdump, + ** "you would have thought promiscuous would be sufficient" + */ + if (dlpromisconreq(&dc, DL_PROMISC_MULTI) < 0 + || dlokack(&dc) < 0) + efatal(1, dc.dc_ebuf); +#endif /* !OSN_USE_IPONLY */ + + /* Find the physical ethernet address of the interface we got. + ** Do it now, because it may be needed in order to set up the + ** correct packet filter (sigh). + */ + if (dladdrreq(&dc, DL_CURR_PHYS_ADDR) < 0 + || dladdrack(&dc, (unsigned char *)curea) < 0) + efatal(1, dc.dc_ebuf); + + /* HACK HACK -- see if ethernet addr already given, and if so, + ** try to set it if different. + */ + if (!ea_isclr(&osnpf->osnpf_ea) + && (ea_cmp(&osnpf->osnpf_ea, curea) != 0)) { + char old[OSN_EASTRSIZ]; + char new[OSN_EASTRSIZ]; + + /* Attempt to set our EN addr */ + eth_adrsprint(old, (unsigned char *)curea); + eth_adrsprint(new, (unsigned char *)&osnpf->osnpf_ea); + + if (dlsetaddrreq(&dc, (unsigned char *)&osnpf->osnpf_ea) < 0 + || dlokack(&dc) < 0) + efatal(1, dc.dc_ebuf); + + /* Double-check by fetching new addr again and using it */ + if (dladdrreq(&dc, DL_CURR_PHYS_ADDR) < 0 + || dladdrack(&dc, (unsigned char *)curea) < 0) + efatal(1, dc.dc_ebuf); + + if (ea_cmp(&osnpf->osnpf_ea, curea) == 0) { + dbprintln("\"%s\" E/N addr changed: Old=%s New=%s", + osnpf->osnpf_ifnam, old, new); + } else { + dbprintln("\"%s\" E/N addr change failed, Old=%s New=%s", + osnpf->osnpf_ifnam, old, new); + } + } + ea_set(&osnpf->osnpf_ea, curea); + +#if 0 + /* Ensure that each read gives us one packet. + ** Shouldn't matter since we use getmsg(), but code left here in case. + */ + if (ioctl(fd, I_SRDOPT, (char *)RMSGD) < 0) + esfatal(1, "I_SRDOPT failed"); +#endif + + /* Set up packet filter for it - should only be needed if + ** sharing interface, but on Solaris we may be always in promiscuous + ** mode! + */ +#if !CENV_SYS_SOLARIS + if (!osnpf->osnpf_dedic) +#endif + { + struct OSN_PFSTRUCT *pf; + + if (ioctl(fd, I_PUSH, "pfmod") < 0) + esfatal(1, "PUSH of pfmod failed"); + +#if !OSN_USE_IPONLY + if (osnpf->osnpf_dedic) + /* Filter on our ether addr */ + pf = pfeabuild(arg, (unsigned char *)&osnpf->osnpf_ea); + else +#endif + /* Filter on our IP addr */ + pf = pfbuild(arg, &osnpf->osnpf_ip.ia_addr); + + if (strioctl(dc.dc_fd, PFIOCSETF, sizeof(*pf), (char *)pf) < 0) + esfatal(1, "PFIOCSETF failed"); + } + + /* Needed? Flush read side to ensure clear of anything accumulated */ + if (ioctl(fd, I_FLUSH, (char *)FLUSHR) < 0) + esfatal(1, "I_FLUSH failed"); + + /* Ready to roll! */ + return fd; +} + +/* Handy auxiliary to pick up EA address given the PF FD, in case + it's needed again after osn_pfinit is done. + */ +static int +dleaget(int fd, unsigned char *eap) +{ + struct dlpictx dc; + + dc.dc_fd = fd; + if ((dladdrreq(&dc, DL_CURR_PHYS_ADDR) < 0) + || (dladdrack(&dc, (unsigned char *)eap) < 0)) { + error(dc.dc_ebuf); + ea_clr(eap); + return FALSE; + } + return TRUE; +} + +#if OSN_USE_IPONLY /* Apparently only needed for this */ +static int +dlfastpathon(struct dlpictx *dc, int sap) +{ + dl_unitdata_req_t *req; + struct ledladdr { + struct ether_addr dl_phys; + unsigned short dl_sap; + } *dladdrp; + int n; + + /* Construct DL_UNITDATA_REQ primitive. */ + req = (dl_unitdata_req_t *) dc->dc_buf; + req->dl_primitive = DL_UNITDATA_REQ; + req->dl_dest_addr_length = sizeof (short) + ETHER_ADRSIZ; + req->dl_dest_addr_offset = sizeof (dl_unitdata_req_t); + req->dl_priority.dl_min = 0; + req->dl_priority.dl_max = 0; + + /* Set up addr - instead of a specific dest address, just clear it out */ + dladdrp = (struct ledladdr *) + (((char *)req) + req->dl_dest_addr_offset); + dladdrp->dl_sap = sap; + memset(&dladdrp->dl_phys, 0, ETHER_ADRSIZ); + + if ((n = strioctl(dc->dc_fd, DL_IOC_HDR_INFO, + sizeof(*req)+sizeof(*dladdrp), + (char *)req)) < 0) { + esfatal(1, "DL_IOC_HDR_INFO ioctl failed"); + } + return n; +} +#endif /* OSN_USE_IPONLY */ + + +/* Derived variously from TCPDUMP and Sun's ugly DLTX sample program + */ + +static int +strioctl(int fd, int cmd, int len, char *dp) +{ + struct strioctl str; + int rc; + + str.ic_cmd = cmd; + str.ic_timout = INFTIM; + str.ic_len = len; + str.ic_dp = dp; + rc = ioctl(fd, I_STR, &str); + + return (rc < 0) ? rc : str.ic_len; +} + +static int +dl_sendreq(struct dlpictx *dc, char *ptr, int len, char *what) +{ + struct strbuf ctl; + int flags = 0; + + ctl.maxlen = 0; + ctl.len = len; + ctl.buf = ptr; + + if (putmsg(dc->dc_fd, &ctl, (struct strbuf *) NULL, flags) < 0) { + sprintf(dc->dc_ebuf, "%s putmsg failed: %s", + what, dp_strerror(errno)); + return (-1); + } + return (0); +} + +#if OSN_USE_IPONLY +# define DL_MAXWAIT 0 +#else +# define DL_MAXWAIT 15 /* secs to wait for ACK msg */ +#endif + +#if DL_MAXWAIT + +#include + +static void +sigalrm(s) +{ +} +#endif + +static int +strgetmsg(struct dlpictx *dc, + char *caller, + struct strbuf *datap, + int *flagsp) +{ + int rc; + + dc->dc_ctl.maxlen = MAXDLBUF; + dc->dc_ctl.len = 0; + dc->dc_ctl.buf = (char *)dc->dc_buf; + if (flagsp) + *flagsp = 0; + +#if DL_MAXWAIT + signal(SIGALRM, sigalrm); + if (alarm(DL_MAXWAIT) < 0) { + esfatal(1, "%s: alarm set", caller); + } +#endif + + /* Get expected message */ + if ((rc = getmsg(dc->dc_fd, &dc->dc_ctl, datap, flagsp)) < 0) { + sprintf(dc->dc_ebuf, "%s: getmsg - %s", + caller, dp_strerror(errno)); + return -1; + } + +#if DL_MAXWAIT + if (alarm(0) < 0) { + esfatal(1, "%s: alarm clear", caller); + } +#endif + + /* Got it - now do paranoia check */ + if (rc & (MORECTL | MOREDATA)) { + sprintf(dc->dc_ebuf, "%s: getmsg returned%s%s", + caller, + ((rc & MORECTL) ? " MORECTL" : ""), + ((rc & MOREDATA) ? " MOREDATA" : "")); + return -1; + } + + /* Yet more paranoia - control info should exist */ + /* Take this out if all callers check length */ + if (dc->dc_ctl.len < sizeof (long)) { + sprintf(dc->dc_ebuf, "%s: getmsg ctl.len too short: %d", + dc->dc_ctl.len); + return -1; + } + return 0; +} +#undef DL_MAXWAIT + +static int +dlattachreq(struct dlpictx *dc, long ppa) +{ + dl_attach_req_t req; + + req.dl_primitive = DL_ATTACH_REQ; + req.dl_ppa = ppa; + return dl_sendreq(dc, (char *)&req, sizeof(req), "dlattach"); +} + +static int +dlokack(struct dlpictx *dc) +{ + union DL_primitives *dlp; + + if (strgetmsg(dc, "dladdrack", NULL, NULL) < 0) + return -1; + + dlp = (union DL_primitives *) dc->dc_ctl.buf; + + if (dlp->dl_primitive != DL_OK_ACK) { + sprintf(dc->dc_ebuf, "dlokack unexpected primitive %0lX", + (long)dlp->dl_primitive); + return -1; +#if 0 + /* Possibly insert general error handler (DLTX) */ + if (dlp->dl_primitive == DL_ERROR_ACK) + dlerror(dlp->error_ack.dl_errno); + else + printdlprim(dlp); +#endif + } + + if (dc->dc_ctl.len != sizeof(dl_ok_ack_t)) { + sprintf(dc->dc_ebuf, "dlokack incorrect size %ld", + (long)dc->dc_ctl.len); + return -1; + } + return 0; +} + +static int +dlbindreq(struct dlpictx *dc, + u_long sap, + u_long max_conind, + unsigned short service_mode, + unsigned short conn_mgmt) +{ + dl_bind_req_t req; + + req.dl_primitive = DL_BIND_REQ; + req.dl_sap = sap; + req.dl_max_conind = max_conind; + req.dl_service_mode = service_mode; + req.dl_conn_mgmt = conn_mgmt; + req.dl_xidtest_flg = 0; + + return dl_sendreq(dc, (char *)&req, sizeof(req), "dlbind"); +} + +static int +dlbindack(struct dlpictx *dc) +{ + union DL_primitives *dlp; + + if (strgetmsg(dc, "dlbindack", NULL, NULL) < 0) + return -1; + + dlp = (union DL_primitives *) dc->dc_ctl.buf; + + if (dlp->dl_primitive != DL_BIND_ACK) { + sprintf(dc->dc_ebuf, "dlbindack unexpected response %0lX", + (long)dlp->dl_primitive); + return -1; + } + + if (dc->dc_ctl.len < sizeof (dl_bind_ack_t)) { + sprintf(dc->dc_ebuf, "dlbindack: short response: %d", + dc->dc_ctl.len); + return -1; + } +#if 0 /* Don't understand this */ + if (flags != RS_HIPRI) { + sprintf(dc->dc_ebuf, "dlbindack: DL_OK_ACK was not M_PCPROTO"); + return -1; + } +#endif + return 0; +} + + +static int +dlpromisconreq(struct dlpictx *dc, u_long level) +{ + dl_promiscon_req_t req; + + req.dl_primitive = DL_PROMISCON_REQ; + req.dl_level = level; + return dl_sendreq(dc, (char *)&req, sizeof(req), "dlpromiscon"); +} + +/* + * type arg: DL_FACT_PHYS_ADDR for "factory" address, + * DL_CURR_PHYS_ADDR for actual current address. + */ +static int +dladdrreq(struct dlpictx *dc, int type) +{ + dl_phys_addr_req_t req; + + req.dl_primitive = DL_PHYS_ADDR_REQ; + req.dl_addr_type = type; /* DL_{FACT,CURR}_PHYS_ADDR */ + return dl_sendreq(dc, (char *)&req, sizeof(req), "dladdr"); +} + +static int +dladdrack(struct dlpictx *dc, unsigned char *addr) +{ + union DL_primitives *dlp; + unsigned char *ucp; + int len; + + if (strgetmsg(dc, "dladdrack", NULL, NULL) < 0) + return -1; + + dlp = (union DL_primitives *) dc->dc_ctl.buf; + + if (dlp->dl_primitive != DL_PHYS_ADDR_ACK) { + sprintf(dc->dc_ebuf, "dladdrack unexpected response %0lX", + (long)dlp->dl_primitive); + return -1; + } + if (dc->dc_ctl.len < sizeof (dl_phys_addr_ack_t)) { + sprintf(dc->dc_ebuf, "dladdrack: short response: %d", + dc->dc_ctl.len); + return -1; + } + + /* Hardwired paranoia check, assume ethernet for now */ + len = dlp->physaddr_ack.dl_addr_length; + if (len != ETHER_ADRSIZ) { + sprintf(dc->dc_ebuf, "dladdrack: non-Ether addr len %d", len); + return (-1); + } + ucp = ((unsigned char *)dlp) + dlp->physaddr_ack.dl_addr_offset; + memcpy(addr, ucp, (size_t)len); + + return 0; +} + +static int +dlsetaddrreq(struct dlpictx *dc, unsigned char *addr) +{ + /* Request is bigger than struct def; overlay it in buffer */ + dl_set_phys_addr_req_t *req = (dl_set_phys_addr_req_t *)dc->dc_buf; + + req->dl_primitive = DL_SET_PHYS_ADDR_REQ; + req->dl_addr_length = ETHER_ADRSIZ; + req->dl_addr_offset = sizeof(dl_set_phys_addr_req_t); + memcpy(&dc->dc_buf[req->dl_addr_offset], addr, ETHER_ADRSIZ); + + return dl_sendreq(dc, (char *)req, sizeof(*req) + ETHER_ADRSIZ, + "dlsetaddr"); +} + +#endif /* KLH10_NET_DLPI */ + +#if 0 /* KLH10_NET_DLPI */ + +/* DLPI functions not currently used, but kept around in case + */ + +/* Turn off promiscuous mode + */ +static int +dlpromiscoff(struct dlpictx *dc) +{ + dl_promiscoff_req_t req; + + req.dl_primitive = DL_PROMISCOFF_REQ; + return dl_sendreq(dc, (char *)&req, sizeof(req), "dlpromiscoff"); +} + + +static int +dlinforeq(struct dlpictx *dc) +{ + dl_info_req_t req; + + req.dl_primitive = DL_INFO_REQ; + return (dl_sendreq(dc, (char *)&req, sizeof(req), "info")); +} + +static int +dlinfoack(struct dlpictx *dc) +{ + union DL_primitives *dlp; + + if (strgetmsg(dc, "dlinfoack", NULL, NULL) < 0) + return -1; + + dlp = (union DL_primitives *) dc->dc_ctl.buf; + if (dlp->dl_primitive != DL_INFO_ACK) { + sprintf(dc->dc_ebuf, "dlinfoack unexpected primitive %ld", + (long)dlp->dl_primitive); + return -1; + } + + /* Extra stuff like the broadcast address can be returned */ + if (dc->dc_ctl.len < DL_INFO_ACK_SIZE) { + sprintf(dc->dc_ebuf, "dlinfoack: incorrect size %ld", + (long)dc->dc_ctl.len); + return -1; + } + return 0; +} + +static int +dlenabmulti(struct dlpictx *dc, + char *addrp, + int len) +{ + dl_enabmulti_req_t *req; + + req = (dl_enabmulti_req_t *) dc->dc_buf; + req->dl_primitive = DL_ENABMULTI_REQ; + req->dl_addr_length = len; + req->dl_addr_offset = sizeof (dl_enabmulti_req_t); + memcpy((dc->dc_buf + sizeof(dl_enabmulti_req_t)), addrp, len); + + return (dl_sendreq(dc, (char *)req, sizeof(*req)+len, "dlenabmulti")); +} + +static int +dldisabmulti(struct dlpictx *dc, + char *addrp, + int len) +{ + dl_disabmulti_req_t *req; + + req = (dl_disabmulti_req_t *) dc->dc_buf; + req->dl_primitive = DL_DISABMULTI_REQ; + req->dl_addr_length = len; + req->dl_addr_offset = sizeof (dl_disabmulti_req_t); + memcpy((dc->dc_buf + sizeof(dl_disabmulti_req_t)), addrp, len); + + return (dl_sendreq(dc, (char *)req, sizeof(*req)+len, "dldisabmulti")); +} + +static int +dldetachreq(struct dlpictx *dc) +{ + dl_detach_req_t req; + + req.dl_primitive = DL_DETACH_REQ; + return (dl_sendreq(dc, (char *)&req, sizeof(req), "dldetach")); +} + +static int +dlunbindreq(struct dlpictx *dc) +{ + dl_unbind_req_t req; + + req.dl_primitive = DL_UNBIND_REQ; + return (dl_sendreq(dc, (char *)&req, sizeof(req), "dlunbind")); +} + +#endif /* KLH10_NET_DLPI */ + +/* Auxiliary OSDNET.C stuff stops here */ + +#if 0 /* Limit of included code so far */ + +/* Both need to dump packets out + */ +void +dumppkt(unsigned char *ucp, int cnt) +{ + int i; + + while (cnt > 0) { + for (i = 8; --i >= 0 && cnt > 0;) { + if (--cnt >= 0) + fprintf(stderr, " %02x", *ucp++); + if (--cnt >= 0) + fprintf(stderr, "%02x", *ucp++); + } + fprintf(stderr, "\r\n"); + } +} + +/* Merge DPNI20's arp_myreply() with DPIMP's arp_req() ?? + But one sends out a reply, the other a request... + */ + +/* There was an ether_write() for NIT unused in dpni20.c + whereas dpimp.c still uses it for everything. +*/ + +#endif /* if 0 - still-excluded code */ diff --git a/src/osdnet.h b/src/osdnet.h new file mode 100644 index 0000000..4f0ab26 --- /dev/null +++ b/src/osdnet.h @@ -0,0 +1,386 @@ +/* OSDNET.H - OS Dependent Network definitions +*/ +/* $Id: osdnet.h,v 2.5 2001/11/19 10:34:01 klh Exp $ +*/ +/* Copyright © 1999, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: osdnet.h,v $ + * Revision 2.5 2001/11/19 10:34:01 klh + * Solaris port fixups. Removed conflicting ether/arp includes. + * + * Revision 2.4 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* XXX Eventually standardize facility names to use OSDNET_ or OSN_ + * and remove KLH10_ references. + */ + +#ifndef OSDNET_INCLUDED +#define OSDNET_INCLUDED 1 + +#ifdef RCSID + RCSID(osdnet_h,"$Id: osdnet.h,v 2.5 2001/11/19 10:34:01 klh Exp $") +#endif + +#include "klh10.h" /* Ensure have config params */ + +/* Determine whether only doing IP stuff, or if all ethernet interface + * capabilities are desired. + */ +#ifndef OSN_USE_IPONLY +# define OSN_USE_IPONLY 0 /* Default is to include everything */ +#endif + +/* Determine net ifc to compile for - NIT, DLPI, PFLT, BPF, TUN, LNX + */ +#ifndef KLH10_NET_BPF /* OSF/1 Berkeley Packet Filter */ +# define KLH10_NET_BPF 0 +#endif +#ifndef KLH10_NET_PFLT /* OSF/1 CMU/Stanford packetfilter */ +# define KLH10_NET_PFLT 0 +#endif +#ifndef KLH10_NET_NIT /* SunOS Network Interface Tap */ +# define KLH10_NET_NIT 0 +#endif +#ifndef KLH10_NET_DLPI /* Solaris Data Link Provider Interface */ +# define KLH10_NET_DLPI 0 +#endif +#ifndef KLH10_NET_TUN /* BSD IP Tunnel device */ +# define KLH10_NET_TUN 0 +#endif +#ifndef KLH10_NET_LNX /* Linux PF_PACKET interface */ +# define KLH10_NET_LNX 0 +#endif + +#if !(KLH10_NET_NIT || KLH10_NET_DLPI || KLH10_NET_BPF || KLH10_NET_PFLT || \ + KLH10_NET_TUN || KLH10_NET_LNX) + /* None explicitly specified, pick a reasonable default */ +# if (CENV_SYS_FREEBSD && OSN_USE_IPONLY) +# undef KLH10_NET_TUN +# define KLH10_NET_TUN 1 +# elif (CENV_SYS_NETBSD || CENV_SYS_FREEBSD) +# undef KLH10_NET_BPF +# define KLH10_NET_BPF 1 +# elif CENV_SYS_DECOSF +# undef KLH10_NET_PFLT +# define KLH10_NET_PFLT 1 +# elif CENV_SYS_SUN +# undef KLH10_NET_NIT +# define KLH10_NET_NIT 1 +# elif CENV_SYS_SOLARIS +# undef KLH10_NET_DLPI +# define KLH10_NET_DLPI 1 +# elif CENV_SYS_LINUX +# undef KLH10_NET_LNX +# define KLH10_NET_LNX 1 +# else +# error "Must specify a KLH10_NET_ configuration" +# endif +#endif + + +/* Ensure this is defined in order to get right stuff for DECOSF */ +#define _SOCKADDR_LEN + +#if CENV_SYS_UNIX +# include /* Standard Unix syscalls */ +# include +# include /* For O_RDWR */ +# include +# include +# include +# include +# include /* For ntohl, htonl, in_addr */ +# include /* Semi-std defs for: + ether_addr, ether_header, ether_arp */ + +# define ossock_t int /* No typedef until code revised */ +#endif /* CENV_SYS_UNIX */ + +#if KLH10_NET_NIT +# include /* For stream operations */ +# include /* For NIT */ +# include /* For NIT */ +# include /* For packet filtering */ +# include /* For packet filtering */ + +#elif KLH10_NET_DLPI +# include +# include +# include +# include /* For packet filtering */ +# include + +#elif KLH10_NET_PFLT +# include + +#elif KLH10_NET_BPF +# include +# include +# include +# include + +#elif KLH10_NET_LNX +# include +# include +# include /* for the glibc version number */ +# if __GLIBC__ >= 2 && __GLIBC_MINOR >= 1 +# include +# include /* the L2 protocols */ +# else +# include +# include +# include /* The L2 protocols */ +# endif +#endif + + +#ifndef FALSE +# define FALSE 0 +#endif +#ifndef TRUE +# define TRUE 1 +#endif + +/* Additional definitions that need to be shared by both + osdnet.c and its including module (dpni20 or dpimp). + XXX This location and the names are temporary until the OSDNET API is + made more complete and opaque. +*/ +#if KLH10_NET_BPF +# include + +/* MTU to use for input, rounded the way BPF wants it. */ +# define OSN_BPF_MTU \ + (BPF_WORDALIGN(1514) + BPF_WORDALIGN(sizeof(struct bpf_hdr))) + +#endif + +/* Packet filter definitions */ + +/* IP packet byte offsets, network order */ +#define IPBOFF_SRC (3*4) /* Byte offset to start of IP source addr */ +#define IPBOFF_DEST (4*4) /* Byte offset to start of IP dest addr */ + +/* Packet byte offsets for interesting fields (in network order) */ +#define PKBOFF_EDEST 0 /* 1st shortword of Ethernet destination */ +#define PKBOFF_ETYPE 12 /* Shortwd offset to Ethernet packet type */ +#define PKBOFF_SAPS 14 /* Shortwd offset to DSAP/SSAP if 802.3 */ +#define PKBOFF_IPDEST (14+IPBOFF_DEST) /* 1st byte of IP dest */ + +/* Packet shortword offsets for interesting fields */ +#define PKSWOFF_EDEST 0 /* 1st shortword of Ethernet destination */ +#define PKSWOFF_ETYPE 6 /* Shortwd offset to Ethernet packet type */ +#define PKSWOFF_SAPS 7 /* Shortwd offset to DSAP/SSAP if 802.3 */ +#define PKSWOFF_IPDEST (7+(IPBOFF_DEST/2)) /* 1st (high) sw of IP dest */ + + +/* Determine whether: + * (1) sockaddr contains sa_len (NETIF_HAS_SALEN) + * (2) ifconf provides physical link addrs (NETIF_HAS_ETHLINK) + * (3) ARP ioctls exist (NETIF_HAS_ARPIOCTL) + * + * 4.4BSD DECOSF SunOS Solaris Linux + * NETIF_HAS_SALEN yes yes no no no + * NETIF_HAS_ETHLINK yes yes no no no + * NETIF_HAS_ARPIOCTL no yes yes yes yes + */ +#ifndef NETIF_HAS_SALEN /* If not explicitly told, see if known OS */ +# if CENV_SYS_XBSD || CENV_SYS_DECOSF +# define NETIF_HAS_SALEN 1 +# elif CENV_SYS_SUN || CENV_SYS_SOLARIS || CENV_SYS_LINUX +# define NETIF_HAS_SALEN 0 +# endif +#endif +#ifndef NETIF_HAS_SALEN /* If still not defined, try to guess */ +# ifdef AF_LINK /* Existence of this implies we can win */ +# define NETIF_HAS_SALEN 1 +# else +# define NETIF_HAS_SALEN 0 +# endif +#endif + +#if NETIF_HAS_SALEN +# ifdef AF_LINK +# include /* For sockaddr_dl */ +# endif +# ifdef LLADDR /* Double-check, make sure this is defined */ +# define NETIF_HAS_ETHLINK 1 +# else +# define NETIF_HAS_ETHLINK 0 +# endif +#else +# define NETIF_HAS_ETHLINK 0 +#endif + +#ifdef SIOCGARP +# define NETIF_HAS_ARPIOCTL 1 +#else +# define NETIF_HAS_ARPIOCTL 0 +#endif + + +/* Interface table entry. + This is needed to provide a more generic format that can + be used regardless of the actual system being compiled for. + Note re interface name: kernel code (at least in FreeBSD) is schizo + as to whether it is always null-terminated or not. Names shorter + than IFNAMSIZ chars are always terminated, but in at least some places + the kernel allows the name to occupy a full IFNAMSIZ chars with no + terminating null byte, so the format here must allow for that. + */ +struct ifent { + char ife_name[IFNAMSIZ+1]; /* +1 so always null-terminated */ + int ife_flags; /* IFF_ flags */ + int ife_mtu; /* MTU (not really used) */ + union { + struct in_addr ifeu_ia; + unsigned char ifeu_chr[4]; + } ife_uip; + int ife_gotea; /* TRUE if E/N addr set */ + unsigned char ife_ea[6]; /* E/N address */ + + struct ifreq *ife_pinet; /* Reference pointer to inet ifreq */ + struct ifreq *ife_plink; /* Reference pointer to link ifreq */ + struct ifreq *ife_pother; /* Reference pointer to ???? ifreq */ +}; +#define ife_ipia ife_uip.ifeu_ia /* IP address as in_addr */ +#define ife_ipint ife_uip.ifeu_ia.s_addr /* IP address as integer */ +#define ife_ipchr ife_uip.ifeu_chr /* IP address as bytes */ + + +/* Option arguments to iftab_init */ +#define IFTAB_IPS 0x1 /* Accept IP-bound interface */ +#define IFTAB_ETHS 0x2 /* Accept Ether-like LINK interface */ +#define IFTAB_OTH 0x4 /* Accept all other interfaces */ +#define IFTAB_ALL (IFTAB_IPS|IFTAB_ETHS|IFTAB_OTH) + +/* Define a variety of utilities to manipulate IP and Ethernet +** addresses and headers. These need to be provided for portability +** because not all platforms define a "struct ether_addr" and even +** the venerable "struct in_addr" can vary. +** +** Furthermore, code which attempts to impose a structure on top of +** a raw data stream is inherently unportable. Despite the fact +** it usually works on most platforms, ANSI C does not guarantee +** that, for example, the typical "struct ether_addr" will always +** have a sizeof 6. Depending on the platform alignment, it could +** legally be larger. +*/ + +/* Ethernet packet definitions */ + +#define ETHER_ADRSIZ 6 /* # bytes in ethernet address */ +#define ETHER_HDRSIZ 14 /* # bytes in header (2 addrs plus type) */ +#define ETHER_MTU 1500 /* Max # data bytes in ethernet pkt */ +#define ETHER_MIN (60-ETHER_HDRSIZ) /* Minimum # data bytes */ +#define ETHER_CRCSIZ 4 /* # bytes in trailing CRC */ + + /* Ethernet packet offset values */ +#define ETHER_PX_DST 0 /* Dest address */ +#define ETHER_PX_SRC 6 /* Source address */ +#define ETHER_PX_TYP 12 /* Type (high byte first) */ +#define ETHER_PX_DAT 14 /* Data bytes */ + /* CRC comes after data, which is variable-length */ + +#if KLH10_NET_BPF && !(CENV_SYS_SUN || CENV_SYS_NETBSD || CENV_SYS_FREEBSD) + /* For compatibility with SunOS definition. + Needed for BPF, but most BSD-ish systems already define it?? + Not really sure why this is here. + */ +struct ether_addr { unsigned char crud[ETHER_ADRSIZ]; }; +#endif + +/* Ethernet address. Use ETHER_ADRSIZ for actual size. */ +struct eth_addr { + unsigned char ea_octets[ETHER_ADRSIZ]; +}; + +/* Ethernet header. Use ETHER_HDRSIZ for actual size. */ +struct eth_header { + unsigned char eh_octets[ETHER_HDRSIZ]; +}; + +#define ea_ptr(var) (&(var[0])) +#define ea_set(ea, from) memcpy((void *)(ea), (void *)(from), ETHER_ADRSIZ) +#define ea_clr(ea) memset((void *)(ea), 0, ETHER_ADRSIZ) +#define ea_cmp(ea, ea2) memcmp((void *)(ea), (void *)(ea2), ETHER_ADRSIZ) +#define ea_isclr(ea) (0==memcmp((void *)(ea), "\0\0\0\0\0\0", ETHER_ADRSIZ)) + +#define eh_dptr(ehp) (((unsigned char *)(ehp))+ETHER_PX_DST) +#define eh_sptr(ehp) (((unsigned char *)(ehp))+ETHER_PX_SRC) +#define eh_tptr(ehp) (((unsigned char *)(ehp))+ETHER_PX_TYP) + +#define eh_dset(ehp, eap) ea_set(eh_dptr(ehp), (eap)) +#define eh_sset(ehp, eap) ea_set(eh_sptr(ehp), (eap)) +#define eh_tset(ehp, typ) ((eh_tptr(ehp)[0] = ((typ)>>8)&0377), \ + (eh_tptr(ehp)[1] = (typ)&0377)) + +#define eh_dget(ehp, eap) ea_set((eap), eh_dptr(ehp)) +#define eh_sget(ehp, eap) ea_set((eap), eh_sptr(ehp)) +#define eh_tget(ehp) ((eh_tptr(ehp)[0] << 8) | (eh_tptr(ehp)[1])) + + +/* Internet Protocol definitions */ + +#define IP_ADRSIZ 4 /* # bytes in IP address */ + +union ipaddr { + unsigned char ia_octet[IP_ADRSIZ]; + struct in_addr ia_addr; +}; + +int osn_iftab_init(int); +int osn_nifents(void); /* # of entries cached by osn_iftab_init */ + +int osn_ifsock(char *ifnam, ossock_t *); +int osn_ifclose(ossock_t); +void osn_iftab_show(FILE *f, struct ifent *ife, int nents); +void osn_ifctab_show(FILE *f, struct ifconf *ifc); +int osn_ifipget(int s, char *ifnam, unsigned char *ipa); +int osn_ifnmget(int s, char *ifnam, unsigned char *ipa); +int osn_pfeaget(int s, char *ifnam, unsigned char *eap); +int osn_ifeaget(int s, char *ifnam, unsigned char *eap, unsigned char *def); +#if !OSN_USE_IPONLY +int osn_ifeaset(int s, char *ifnam, unsigned char *newpa); +int osn_ifmcset(int s, char *ifc, int delf, unsigned char *pa); +#endif + +struct ifent *osn_ipdefault(void); +struct ifent *osn_iftab_arp(struct in_addr ia); +int osn_iftab_arpget(struct in_addr ia, unsigned char *eap); + +#define OSN_EASTRSIZ sizeof("xx:xx:xx:xx:xx:xxZZ") +char *eth_adrsprint(char *cp, unsigned char *ea); + +#define OSN_IPSTRSIZ sizeof("ddd.ddd.ddd.dddZZZZ") +char *ip_adrsprint(char *cp, unsigned char *ip); + +int osn_arp_stuff(unsigned char *ipa, unsigned char *eap, int pubf); +int osn_arp_look(struct in_addr *ipa, unsigned char *eap); + +struct osnpf { /* Arg struct for common initialization params */ + char *osnpf_ifnam; /* Interface name */ + int osnpf_dedic; /* TRUE if dedicated ifc, else shared */ + int osnpf_rdtmo; /* Read timeout, if any */ + int osnpf_backlog; /* Allow # backlogged packets, if any */ + union ipaddr osnpf_ip; /* IP address to use */ + struct ether_addr osnpf_ea; /* OUT: ether address of ifc */ +}; +int osn_pfinit(struct osnpf *, void *); + +#endif /* ifndef OSDNET_INCLUDED */ diff --git a/src/osdsup.c b/src/osdsup.c new file mode 100644 index 0000000..5a3070f --- /dev/null +++ b/src/osdsup.c @@ -0,0 +1,1603 @@ +/* OSDSUP.C - OS-Dependent Support for KLH10 +*/ +/* $Id: osdsup.c,v 2.6 2001/11/19 12:09:58 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: osdsup.c,v $ + * Revision 2.6 2001/11/19 12:09:58 klh + * Disable shared mem hacking if no DPs + * + * Revision 2.5 2001/11/19 10:43:28 klh + * Add os_rtm_adjust_base for ITS on Mac + * + * Revision 2.4 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#include +#include /* For strrchr */ + +#include "klh10.h" +#include "kn10def.h" +#include "osdsup.h" +#include "kn10ops.h" + +#if CENV_SYS_UNIX +# include +# include +# include + +# include +# include +# include /* For struct tm, localtime() */ + +# if CENV_SYSF_BSDTIMEVAL +# include /* BSD: For setitimer() */ +# include /* BSD: For getrusage() */ +# endif + +# if CENV_SYS_SOLARIS +# include +# include +# endif +# if CENV_SYS_SOLARIS || CENV_SYS_XBSD || CENV_SYS_LINUX +# if CENV_SYS_LINUX +# include /* For FIONREAD */ +# else +# include /* For FIONREAD */ +# endif +# include /* BSD: For setitimer() */ +# include /* No getrusage(), use times() */ +# include /* For CLK_TCK */ +# endif + +# if CENV_SYSF_TERMIOS +# include /* SV4: For terminal stuff */ +# else +# include +# endif + + /* Declare syscalls used */ +# if CENV_SYS_UNIX && !CENV_SYS_V7 +# include /* read, write, lseek */ +# include /* ioctl, stty, gtty */ +# include /* getrusage */ + /* sys/stat.h: fstat */ + /* sys/time.h: gettimeofday, setitimer */ +# else + extern int gtty(), stty(), ioctl(), read(), write(), lseek(), fstat(); + extern int gettimeofday(), setitimer(), getrusage(); +# endif + +#elif CENV_SYS_MAC +# include +# include +# include +# include +# include +# include +# include + +# include "timer-glue.h" + +#if CENV_USE_COMM_TOOLBOX + extern void InitializeMac(); + extern void (*HaltRoutine)(); + extern void CheckEvents(Boolean idle); + extern Boolean tty_crlf_mode; + extern int keyboard_buffer_fill_pointer; +#endif /* CENV_USE_COMM_TOOLBOX */ +#endif /* CENV_SYS_MAC */ + +#if CENV_SYSF_STRERROR + extern char *strerror(int); /* Not always declared in string.h */ +#endif + +extern void fe_shutdown(void); + +#ifdef RCSID + RCSID(osdsup_c,"$Id: osdsup.c,v 2.6 2001/11/19 12:09:58 klh Exp $") +#endif + +/* OS-dependent program startup and exit. +** Surprisingly, "main" is really an OSD function! +** Note that klh10_main is a void function; exit is performed by calling +** os_exit() with a status value. +** If it becomes necessary for program exit to do so by returning +** a value from main(), this can be accomplished by having os_exit +** do a longjmp back to the main() here. +*/ + +extern void klh10_main(int, char **); /* In klh10.c */ + +int +main(int argc, char **argv) +{ +# if CENV_SYS_MAC /* Increase stack size on a Mac */ + size_t *base = (size_t *) 0x000908; + SetApplLimit ((Ptr) (*base - (size_t) 65535L)); +# endif + klh10_main(argc, argv); + return 0; +} + +void +os_init(void) +{ +#if CENV_SYS_UNIX /* Just return error if write to pipe fails */ + osux_signal(SIGPIPE, SIG_IGN); +#elif CENV_SYS_MAC + InitializeMac(); +#endif +} + +void +os_exit(int status) +{ +#if CENV_SYS_MAC && CENV_USE_COMM_TOOLBOX + os_timer(0, NULL, 0, NULL); /* Stop the timer. Important! */ + if (status) { + os_ttycmforce(); /* Badness. Give user a chance */ + for (;;) CheckEvents(TRUE); /* to read final message */ + } +#endif + exit(status); +} + + +char * +os_strerror(int err) +{ + if (err == -1 && errno != err) + return os_strerror(errno); +#if CENV_SYSF_STRERROR + return strerror(err); +#else +# if CENV_SYS_UNIX + { +# if !CENV_SYS_XBSD /* Already in signal.h */ + extern int sys_nerr; + extern char *sys_errlist[]; +# endif + if (0 < err && err <= sys_nerr) + return sys_errlist[err]; + } +# endif + if (err == 0) + return "No error"; + else { + static char ebuf[30]; + sprintf(ebuf, "Unknown-error-%d", err); + return ebuf; + } +#endif /* !CENV_SYSF_STRERROR */ +} + +/* Controlling terminal stuff +** +** Note current hack to support background mode without changing a lot of +** other stuff. If the local "ttyback" flag is set, all os_tty functions +** pretend to work but don't actually do anything other than output, which +** is assumed to have been redirected on the command line to some file/pipe. +** +** This should be fixed later to introduce another layer of TTY support, which +** can then be moved to a module that knows how to accept commands from +** a socket or file. The OS_TTY functions should then be restored to their +** former low-level operations (to enhance portability) which the higher +** FE/CTY layer can then use as needed. +*/ + +static int ttyback = FALSE; /* Initially false, not in background */ + +#if CENV_SYS_UNIX +static int ttystated = FALSE; + +static struct ttystate { +# if CENV_SYSF_TERMIOS + struct termios tios; +# else + struct sgttyb sg; +# if CENV_SYSF_BSDTTY + struct tchars t; + struct ltchars lt; +# endif +# endif +} + inistate, /* Initial state at startup, restored on exit */ + cmdstate, /* State while waiting for command char */ + linstate, /* State while collecting command line */ + runstate; /* State while KLH10 running */ + +static void +ttyget(register struct ttystate *ts) +{ +# if CENV_SYSF_TERMIOS + (void) tcgetattr(0, &(ts->tios)); /* Get all terminal attrs */ +# else + + gtty(0, &(ts->sg)); /* Get basic sgttyb state */ +# if CENV_SYSF_BSDTTY + ioctl(0, TIOCGETC, &(ts->t)); /* Get tchars */ + ioctl(0, TIOCGLTC, &(ts->lt)); /* Get ltchars */ +# endif /* CENV_SYSF_BSDTTY */ + +# endif /* !CENV_SYSF_TERMIOS */ +} + +static void +ttyset(register struct ttystate *ts) +{ +# if CENV_SYSF_TERMIOS + (void) tcsetattr(0, TCSANOW, &(ts->tios)); /* Set all terminal attrs */ + /* Note change is immediate */ +# else + + stty(0, &(ts->sg)); /* Set basic sgttyb state */ +# if CENV_SYSF_BSDTTY + ioctl(0, TIOCSETC, &(ts->t)); /* Set tchars */ + ioctl(0, TIOCSLTC, &(ts->lt)); /* Set ltchars */ +# endif /* CENV_SYSF_BSDTTY */ + +# endif /* !CENV_SYSF_TERMIOS */ +} +#endif /* CENV_SYS_UNIX */ + +#if 0 +/* This routine is needed on systems that inherited the original unix +** genetic defect wherein a caught signal de-installs +** the original handler. +*/ +static void (*intsighan)(); +static void +intresig(void) +{ + signal(SIGINT, intresig); /* Re-install self, sigh! */ + (*intsighan)(); /* Invoke intended handler */ +} +#endif + +/* TTY background mode stuff - see comments at start of page + */ +#if CENV_SYS_UNIX +static void +os_sigtermhdl(int sig) +{ + if (ttyback) { + printf("[SIGTERM received - invoking auto-shutdown!]\n"); + fe_shutdown(); + } +} +#endif /* CENV_SYS_UNIX */ + +void +os_ttybkgd(void) +{ + ttyback = TRUE; /* Say running in bkgd */ + +#if CENV_SYS_UNIX + /* Clear TTY state in case it's ever referenced by accident */ + memset((void *)&inistate, 0, sizeof(inistate)); + ttystated = TRUE; + + (void) osux_signal(SIGTERM, os_sigtermhdl); +#endif /* CENV_SYS_UNIX */ +} + +void +os_ttyinit(ossighandler_t *rtn) +{ +#if CENV_SYS_UNIX + /* Set up SIGINT to trap to FE (KLH10 command processor). + ** SIGINT is used instead of (eg) SIGQUIT because SIGINT is the + ** only appropriate value available from the ANSI C standard. + */ + osux_signal(SIGINT, rtn); /* Use native SIGINT */ + + /* Turn off SIGQUIT since the last thing we want to do with + ** a 32MB data segment is dump core! + */ + osux_signal(SIGQUIT, SIG_IGN); + + if (!ttystated) { /* If first time, */ + ttyget(&inistate); /* remember initial TTY state */ + ttystated = TRUE; + } + + /* Now set up various states as appropriate */ + cmdstate = linstate = runstate = inistate; /* Start with known state */ + + if (!ttyback) { +# if CENV_SYSF_TERMIOS + runstate.tios.c_iflag = IMAXBEL; /* Minimal input processing */ + runstate.tios.c_oflag &= ~OPOST; /* No output processing */ + runstate.tios.c_cflag &= ~(CSIZE|PARENB|PARODD); + runstate.tios.c_cflag |= CS8; /* No parity, 8-bit chars */ + runstate.tios.c_lflag = ISIG; /* Allow just ctl sigs */ + memset(runstate.tios.c_cc, -1, sizeof(runstate.tios.c_cc)); + runstate.tios.c_cc[VINTR] = cpu.fe.fe_intchr; + runstate.tios.c_cc[VMIN] = 1; + runstate.tios.c_cc[VTIME] = 0; + +# else + cmdstate.sg.sg_flags |= CBREAK; /* Want CBREAK for cmds */ + runstate.sg.sg_flags |= CBREAK; /* and running */ + runstate.sg.sg_flags &= ~(ECHO|CRMOD); /* no echo when running */ + +# if CENV_SYSF_BSDTTY + /* If a specific command escape/interrupt char is set, use that and + ** turn off everything else. If 0, not set, all chars available for + ** possible debugging. + */ + if (cpu.fe.fe_intchr) { + runstate.t.t_intrc = cpu.fe.fe_intchr; + runstate.t.t_quitc = + runstate.t.t_startc = + runstate.t.t_stopc = + runstate.t.t_eofc = -1; + + /* At this point, OK for cmd and cmdline input */ + linstate.t = cmdstate.t = runstate.t; + linstate.lt = cmdstate.lt = runstate.lt; + + /* Now finish clearing decks for run state */ + runstate.t.t_brkc = -1; + runstate.lt.t_suspc = + runstate.lt.t_dsuspc = + runstate.lt.t_rprntc = + runstate.lt.t_flushc = + runstate.lt.t_werasc = + runstate.lt.t_lnextc = -1; + } +# endif /* CENV_SYSF_BSDTTY */ +# endif /* !CENV_SYSF_TERMIOS */ + + } /* end of if(!ttyback) */ + +#elif CENV_SYS_MAC +# if CENV_USE_COMM_TOOLBOX + HaltRoutine = rtn; /* Command-. calls HaltRoutine, */ + tty_crlf_mode = FALSE; /* returning to FE cmd level */ +# else + intsighan = rtn; /* Remember actual handler to use */ + signal(SIGINT, intresig); /* Command-. returns to FE cmd level */ +# endif + +#else +# error "Unimplemented OS routine os_ttyinit()" +#endif +} + +#if KLH10_CTYIO_INT +void +os_ttysig(ossighandler_t *rtn) +{ + if (ttyback) return; + +# if CENV_SYS_DECOSF || CENV_SYS_SUN || CENV_SYS_XBSD || CENV_SYS_LINUX + osux_signal(SIGIO, rtn); +# elif CENV_SYS_SOLARIS + osux_signal(SIGPOLL, rtn); +# else +# error "Unimplemented OS routine os_ttysig()" +# endif +} +#endif /* KLH10_CTYIO_INT */ + +void +os_ttyreset(void) +{ + if (ttyback) return; + +#if CENV_SYS_DECOSF || CENV_SYS_SUN || CENV_SYS_XBSD || CENV_SYS_LINUX +# if KLH10_CTYIO_INT + fcntl(0, F_SETFL, 0); /* Clear asynch-operation flag */ +# endif + ttyset(&inistate); /* Restore original TTY state */ +#elif CENV_SYS_SOLARIS +# if KLH10_CTYIO_INT + ioctl(0, I_SETSIG, 0); /* Clear TTY stream signal behavior */ +# endif + ttyset(&inistate); /* Restore original TTY state */ + +#elif CENV_SYS_MAC +# if CENV_USE_COMM_TOOLBOX + os_ttycmforce(); + tty_crlf_mode = FALSE; +# else + csetmode(C_ECHO, stdin); /* line buffering */ +# endif + +#else +# error "Unimplemented OS routine os_ttyreset()" +#endif +} + +void +os_ttycmdmode(void) +{ + if (ttyback) return; + +#if CENV_SYS_DECOSF || CENV_SYS_SUN || CENV_SYS_XBSD || CENV_SYS_LINUX +# if KLH10_CTYIO_INT + fcntl(0, F_SETFL, 0); /* Clear asynch-operation flag */ +# endif + ttyset(&cmdstate); /* Restore original TTY state */ +#elif CENV_SYS_SOLARIS +# if KLH10_CTYIO_INT + ioctl(0, I_SETSIG, 0); /* Clear TTY stream signal behavior */ +# endif + ttyset(&cmdstate); /* Restore original TTY state */ + +#elif CENV_SYS_MAC +# if CENV_USE_COMM_TOOLBOX + os_ttycmforce(); + tty_crlf_mode = FALSE; +# else + csetmode(C_CBREAK, stdin); /* no line buffering */ +# endif + +# else +# error "Unimplemented OS routine os_ttycmdmode()" +#endif +} + +void +os_ttyrunmode(void) +{ + if (ttyback) return; + +#if CENV_SYS_DECOSF || CENV_SYS_SUN || CENV_SYS_XBSD || CENV_SYS_LINUX +# if KLH10_CTYIO_INT + fcntl(0, F_SETFL, FASYNC); /* Set asynch-operation flag */ +# if CENV_SYS_FREEBSD || CENV_SYS_NETBSD || CENV_SYS_LINUX + /* FreeBSD for sure, NetBSD probably, Linux almost certainly */ + /* On these systems, it isn't sufficient to simply set FASYNC + (what they now call O_ASYNC). The F_SETOWN function must *ALSO* + be called in order to set the process (or process group) that will + receive the SIGIO or SIGURG signal; it doesn't default! Argh!! + On OSF/1 this applies only to SIGURG, which we aren't using. + */ + { + static int doneonce = 0; + if (!doneonce) { + fcntl(0, F_SETOWN, getpid()); + doneonce = TRUE; + } + } +# endif +# endif + ttyset(&runstate); /* Change to "run" TTY state */ +#elif CENV_SYS_SOLARIS +# if KLH10_CTYIO_INT + ioctl(0, I_SETSIG, S_INPUT); /* Set stream to signal on input */ +# endif + ttyset(&runstate); /* Change to "run" TTY state */ + +#elif CENV_SYS_MAC +# if CENV_USE_COMM_TOOLBOX + os_ttycmforce(); + tty_crlf_mode = TRUE; +# else + csetmode(C_RAW, stdin); /* no line buffering */ +# endif + +#else +# error "Unimplemented OS routine os_ttyrunmode()" +#endif +} + +#if CENV_SYS_MAC && !CENV_USE_COMM_TOOLBOX +static int macsavchar = -1; +#endif + +/* OS_TTYINTEST - See if any TTY input available, and returns count of chars. +** If count unknown, OK to return just 1; routine will be invoked +** frequently (either by clock timeout or by I/O signal). +*/ +int +os_ttyintest(void) +{ + if (ttyback) return 0; + +#if CENV_SYS_UNIX + { + /* OSD WARNING: the FIONREAD ioctl is defined to want a "long" on SunOS + ** and presumably old BSD, but it uses an "int" on Solaris and DEC OSF/1! + ** Leave undefined for unknown systems to ensure this is checked on + ** each new port. + */ +#if CENV_SYS_SUN + long retval; +#elif CENV_SYS_SOLARIS || CENV_SYS_DECOSF || CENV_SYS_XBSD || CENV_SYS_LINUX + int retval; +#endif + if (ioctl(0, FIONREAD, &retval) != 0) /* If this call fails, */ + return 0; /* assume no input waiting */ + return (int) retval; + } +#elif CENV_SYS_MAC +# if CENV_USE_COMM_TOOLBOX + if (stdin->buffer_len) + return stdin->buffer_len; + + CheckEvents(FALSE); + return (keyboard_buffer_fill_pointer); +# else + { + unsigned char c; + if (macsavchar >= 0) /* If previously input char is there, */ + return 1; /* say so. */ + if (mactick) + (*mactick)(); /* Do a simulated clock tick (KLH: ?!!) */ + + if (read(0, (char *)&c, 1) != 1) /* If this call fails, */ + return 0; /* assume no input waiting */ + switch (c &= 0177) { /* what did we get? */ + case 034: /* CTRL-\ */ + raise(SIGINT); /* request the console */ + return 0; /* Allow for possible return */ + case 010: /* backspace becomes delete (sigh) */ + c = 0177; + default: + macsavchar = c; /* save character */ + } + return 1; /* have something to read */ + } +# endif + +#else +# error "Unimplemented OS routine os_ttyintest()" +#endif +} + +int +os_ttyin(void) +{ + if (ttyback) { + printf("[TTYIN while in background; invoking auto-shutdown!]\n"); + fe_shutdown(); + } + +#if CENV_SYS_UNIX + { + unsigned char buf; + if (read(0, (char *)&buf, 1) != 1) /* If this call fails, */ + return -1; /* assume no input waiting */ + return buf; + } +#elif CENV_SYS_MAC +# if CENV_USE_COMM_TOOLBOX + if (os_ttyintest()) + return getc(stdin); + else return -1; +# else + { + int c = macsavchar; + macsavchar = -1; /* read the character */ + return c; + } +# endif + +#else +# error "Unimplemented OS routine os_ttyin()" +#endif +} + +int +os_ttyout(int ch) +{ +#if CENV_SYS_UNIX || CENV_SYS_MAC + char chloc = ch & 0177; /* Sigh, must mask off T20 parity */ +# if CENV_USE_COMM_TOOLBOX + /* Event check to allow stopping runaway typeout on Mac */ + static int ttyout_event_check_counter = 1; + if (--ttyout_event_check_counter <= 0) { + CheckEvents(FALSE); + ttyout_event_check_counter = 100; + } +# endif + return write(1, &chloc, 1) == 1; + +#else +# error "Unimplemented OS routine os_ttyout()" +#endif +} + +/* TTY String output. +** Note assumption that 8th bit is already masked, if desired. +** Call may block, sigh. +*/ +int +os_ttysout(char *buf, int len) /* Note length is signed int */ +{ +#if CENV_SYS_UNIX || CENV_SYS_MAC +# if CENV_USE_COMM_TOOLBOX + /* Event check to allow stopping runaway typeout on Mac */ + CheckEvents(FALSE); +# endif + return write(1, buf, (size_t)len) == len; + +#else +# error "Unimplemented OS routine os_ttysout()" +#endif +} + +/* Top-level command character input +** May want to be different from os_ttyin(). +*/ +int +os_ttycmchar(void) +{ + if (ttyback) { + printf("[TTYCMCHAR while in background; invoking auto-shutdown!]\n"); + fe_shutdown(); + } + +#if CENV_SYS_MAC && CENV_USE_COMM_TOOLBOX + { + int ch; + + while(!os_ttyintest()) + CheckEvents(TRUE); + ch = getc(stdin); + os_ttyout(ch); + os_ttycmforce(); + return ch; + } +#else + return getc(stdin); +#endif +} + +char * +os_ttycmline(char *buffer, int size) +{ + if (ttyback) { + printf("[TTYCMLINE while in background; invoking auto-shutdown!]\n"); + fe_shutdown(); + } + +#if CENV_SYS_MAC && CENV_USE_COMM_TOOLBOX + { + /*--- Add rubout processing later ---*/ + int i=0, ch; + + os_ttycmforce(); + --size; /* allow for null at end */ + while (i < size) { + ch = os_ttycmchar(); + if (ch == 015) + break; + else if (ch == 0177) { + printf("XXX\n"); + os_ttycmforce(); + i = 0; + } else + buffer[i++] = ch; + } + buffer[i] = 0; + return buffer; + } +#else + return fgets(buffer, size, stdin); +#endif +} + +void +os_ttycmforce(void) +{ + fflush(stdout); +} + +/* General-purpose System-level I/O. +** It is intended that this level of IO be in some sense the fastest +** or most efficient way to interact with the host OS, as opposed to +** the more portable stdio interface. +*/ + +int +os_fdopen(osfd_t *afd, char *file, char *modes) +{ +#if CENV_SYS_UNIX || CENV_SYS_MAC + int flags = 0; + if (!afd) return FALSE; + for (; modes && *modes; ++modes) switch (*modes) { + case 'r': flags |= O_RDONLY; break; /* Yes I know it's 0 */ + case 'w': flags |= O_WRONLY; break; + case '+': flags |= O_RDWR; break; + case 'a': flags |= O_APPEND; break; + case 'b': /* Binary */ +# if CENV_SYS_MAC + flags |= O_BINARY; +# endif + break; + case 'c': flags |= O_CREAT; break; + /* Ignore unknown chars for now */ + } +# if CENV_SYS_MAC + if ((*afd = open(file, flags)) < 0) +# else + if ((*afd = open(file, flags, 0666)) < 0) +# endif + return FALSE; + return TRUE; + +#else +# error "Unimplemented OS routine os_fdopen()" +#endif +} + +int +os_fdclose(osfd_t fd) +{ +#if CENV_SYS_UNIX || CENV_SYS_MAC + return close(fd) != -1; +#else +# error "Unimplemented OS routine os_fdclose()" +#endif +} + +int +os_fdseek(osfd_t fd, osdaddr_t addr) +{ +#if CENV_SYS_UNIX + return lseek(fd, addr, L_SET) != -1; +#elif CENV_SYS_MAC + return lseek(fd, addr, SEEK_SET) != -1; +#else +# error "Unimplemented OS routine os_fdseek()" +#endif +} + +int +os_fdread(osfd_t fd, + char *buf, + size_t len, size_t *ares) +{ +#if CENV_SYS_UNIX + register int res = read(fd, buf, len); + if (res < 0) { + if (ares) *ares = 0; + return FALSE; + } +#elif CENV_SYS_MAC + /* This is actually generic code for any system supporting unix-like + ** calls with a 16-bit integer count interface. + ** --- I don't think the Mac needs this but I'll leave it anyway --Moon + */ + register size_t res = 0; + register unsigned int scnt, sres = 0; + + while (len) { + scnt = len > (1<<14) ? (1<<14) : len; /* 16-bit count each whack */ + if ((sres = read(fd, buf, scnt)) != scnt) { + if (sres == -1) { /* If didn't complete, check for err */ + if (ares) *ares = res; /* Error, but may have read stuff */ + return FALSE; + } + res += sres; /* No error, just update count */ + break; /* and return successfully */ + } + res += sres; + len -= sres; + buf += sres; + } +#else +# error "Unimplemented OS routine os_fdread()" +#endif + if (ares) *ares = res; + return TRUE; +} + +int +os_fdwrite(osfd_t fd, + char *buf, + size_t len, size_t *ares) +{ +#if CENV_SYS_UNIX + register int res = write(fd, buf, len); + if (res < 0) { + if (ares) *ares = 0; + return FALSE; + } +#elif CENV_SYS_MAC + /* This is actually generic code for any system supporting unix-like + ** calls with a 16-bit integer count interface. + ** --- I don't think the Mac needs this but I'll leave it anyway --Moon + */ + register size_t res = 0; + register unsigned int scnt, sres = 0; + + while (len) { + scnt = len > (1<<14) ? (1<<14) : len; /* 16-bit count each whack */ + if ((sres = write(fd, buf, scnt)) != scnt) { + if (sres == -1) { /* If didn't complete, check for err */ + if (ares) *ares = res; /* Error, but may have written stuff */ + return FALSE; + } + res += sres; /* No error, just update count */ + break; /* and return successfully */ + } + res += sres; + len -= sres; + buf += sres; + } +#else +# error "Unimplemented OS routine os_fdwrite()" +#endif + if (ares) *ares = res; + return TRUE; +} + +/* Support for "atomic" intflag reference. +** Intended to work like EXCH, not always truly atomic but +** close enough. As function to prevent optimizing away +** the swap. +*/ +osintf_t +os_swap(osintf_t *addr, int val) +{ + register int tmp = (int)*addr; + *addr = val; + return tmp; +} + + +/* Support for OS real-time clock + +OS Realtime values: + + BSD Unix and SunOS use a timeval structure with two 32-bit +members, tv_sec and tv_usec. The latter is always modulo 1,000,000 +and fits within 20 bits. + + MacOS uses a 64-bit unsigned integer which counts microseconds. + +*/ + +/* OS_RTMGET - Get OS real time +*/ +int +os_rtmget(register osrtm_t *art) +{ +#if CENV_SYSF_BSDTIMEVAL + if (gettimeofday(art, (struct timezone *)NULL) == 0) + return TRUE; + return FALSE; +#elif CENV_SYS_MAC + Microseconds(art); + return TRUE; +#else +# error "Unimplemented OS routine os_rtmget()" +#endif +} + +/* OS_VRTMGET - Get OS virtual (user CPU) time, in same units as real time. +*/ +int +os_vrtmget(register osrtm_t *art) +{ +#if CENV_SYS_SOLARIS /* Precision sucks, but it's all we have */ + struct tms tms; + + if (times(&tms) == 0) { + art->tv_sec = tms.tms_utime / CLK_TCK; + art->tv_usec = (tms.tms_utime % CLK_TCK) + * (1000000/CLK_TCK); /* Turn into usec */ + return TRUE; + } + return FALSE; +#elif CENV_SYSF_BSDTIMEVAL + /* WARNING!!! Some systems turn out not to report getrusage runtime in a + ** monotonically increasing way! This can result in negative deltas + ** from one get to the next. + ** In particular, this was still true of FreeBSD as of 3.3. + ** See quant_freeze() which contains code to check and recover from this + ** regardless of native OS. + */ + struct rusage rus; + + if (getrusage(RUSAGE_SELF, &rus) == 0) { + *art = rus.ru_utime; /* Return user-time used */ + return TRUE; + } + return FALSE; +#elif CENV_SYS_MAC + Microseconds(art); + return TRUE; +#else +# error "Unimplemented OS routine os_vrtmget()" +#endif +} + +/* OS_RTMSUB - Find difference in OS realtime values +** Does A = A - B; +*/ +void +os_rtmsub(register osrtm_t *a, register osrtm_t *b) +{ +#if CENV_SYSF_BSDTIMEVAL + a->tv_sec -= b->tv_sec; + if ((a->tv_usec -= b->tv_usec) < 0) { + --a->tv_sec; + a->tv_usec += 1000000; + } +#elif CENV_SYS_MAC + unsigned long long avalue, bvalue; + avalue = RTM_PTR_TO_LONG_LONG(a); + bvalue = RTM_PTR_TO_LONG_LONG(b); + avalue -= bvalue; + RTM_PTR_TO_LONG_LONG(a) = avalue; +#else +# error "Unimplemented OS routine os_rtmsub()" +#endif +} + +/* OS_RTM_ADJUST_BASE - Convert an OS realtime value between + * relative and absolute time + */ +void +os_rtm_adjust_base(osrtm_t *in, osrtm_t *out, int b_absolute) +{ +#if CENV_SYS_MAC + /* OS realtimes are relative to system boot on MacOS */ + + unsigned long secs; + unsigned long long longusecs, boot_time; + osrtm_t curtime_relative; + + Microseconds(&curtime_relative); + GetDateTime(&secs); + longusecs = (unsigned long long)secs * (unsigned long long)1000000; + boot_time = longusecs - RTM_PTR_TO_LONG_LONG(&curtime_relative); + + if (b_absolute) + { + /* Make a relative time absolute */ + + RTM_PTR_TO_LONG_LONG(out) = RTM_PTR_TO_LONG_LONG(in) + boot_time; + } + else + { + /* Make an absolute time relative */ + + RTM_PTR_TO_LONG_LONG(out) = RTM_PTR_TO_LONG_LONG(in) - boot_time; + } +#else + /* OS realtimes are always absolute on other OS's */ + + *out = *in; +#endif +} + +/* OS_RTM_TO_SECS, OS_RTM_TO_USECS - Simulate Unix format of + * OS realtime value on Macintosh + */ +#if CENV_SYS_MAC +unsigned long os_rtm_to_secs(osrtm_t rtm) +{ + unsigned long long value = RTM_PTR_TO_LONG_LONG(&rtm); + return value / 1000000; +} + +unsigned long os_rtm_to_usecs(osrtm_t rtm) +{ + unsigned long long value = RTM_PTR_TO_LONG_LONG(&rtm); + return value % 1000000; +} +#endif /* CENV_SYS_MAC */ + + +/* OS_TIMER - Set interval timer interrupt, given interval time in usec. +** Note interval is passed as a uint32. This is big enough for +** 4.29 seconds; all known monitors interrupt at a much faster rate! +** If interval is 0, turn timer interrupt off; the caller has to +** make sure it doesn't pass this value unless that is what is intended! +*/ +void +os_timer(int type, + ossighandler_t *irtn, + register uint32 usecs, + ostimer_t *ostate) +{ +#if CENV_SYSF_BSDTIMEVAL + struct itimerval itm; + + if (type != ITIMER_VIRTUAL) + type = ITIMER_REAL; /* Default is real-time */ + if (ostate) + ostate->ostmr_type = type; + + if (usecs == 0) { + /* Turn timer off */ + timerclear(&itm.it_interval); + timerclear(&itm.it_value); + /* Ignore signals prior to clearing interval timer. + ** Used to be SIG_DFL but this created a danger window since default + ** behavior of both signals is to exit process. + */ + (void) osux_sigact(((type == ITIMER_VIRTUAL) ? SIGVTALRM : SIGALRM), + SIG_IGN, + ostate ? &ostate->ostmr_sigact : NULL); + } else { + itm.it_interval.tv_sec = itm.it_value.tv_sec = usecs / 1000000; + itm.it_interval.tv_usec = itm.it_value.tv_usec = usecs % 1000000; + (void) osux_sigact(((type == ITIMER_VIRTUAL) ? SIGVTALRM : SIGALRM), + irtn, + ostate ? &ostate->ostmr_sigact : NULL); + } + + if (setitimer(type, &itm, ostate ? &ostate->ostmr_itm : NULL) != 0) { + panic("os_timer: setitimer() failed - %s", os_strerror(errno)); + } + +#elif CENV_SYS_MAC + /* Use the MacOS Time Manager */ + static interval_timer* timer = NULL; + + if (usecs == 0) + { /* Turn timer off */ + if (timer) stop_interval_timer(timer); + } + else + { /* Turn timer on (create timer on first use) */ + if (timer == NULL) timer = make_interval_timer(irtn); + start_interval_timer(timer, usecs); + }; + +#else +# error "Unimplemented OS routine os_timer()" +#endif +} + +void +os_rtimer(ossighandler_t *irtn, uint32 usecs) +{ + os_timer(OS_ITIMER_REAL, irtn, usecs, (ostimer_t *)NULL); +} + +void +os_vtimer(ossighandler_t *irtn, uint32 usecs) +{ + os_timer(OS_ITIMER_VIRT, irtn, usecs, (ostimer_t *)NULL); +} + +void +os_timer_restore(ostimer_t *ostate) +{ +#if CENV_SYSF_BSDTIMEVAL && CENV_SYSF_SIGSET + sigset_t blkset, savset; + int ret; + + /* Prevent sigs from going off between handler and timer restoration */ + sigfillset(&blkset); + sigprocmask(SIG_BLOCK, &blkset, &savset); + (void) osux_sigrestore(&ostate->ostmr_sigact); + ret = setitimer(ostate->ostmr_type, &ostate->ostmr_itm, NULL); + (void) sigprocmask(SIG_SETMASK, &savset, (sigset_t *)NULL); + if (ret != 0) { + panic("os_timer_restore: setitimer() failed - %s", os_strerror(errno)); + } +#elif CENV_SYS_MAC + /* no Mac code needed --Moon */ +#else +# error "Unimplemented OS routine os_timer_restore()" +#endif +} + + +/* OS_V2RT_IDLE - special function for clk_idle(). +** Idles for an amount of real time equivalent to however much +** virtual time is left until the next clock interrupt. +** Requires various special hackery - seizes SIGALRM and then assumes +** nothing else changes it (or if it does, it gets restored). +** This is to reduce the overhead of setting up the handler every time. +** Note non-modular reference to INSBRKTEST() - may be able to +** flush it if stats show it's rarely a useful test. +** NOTE: perhaps use nanosleep() here if it exists? +*/ +void +os_v2rt_idle(ossighandler_t *hdlarg) +{ +#if CENV_SYSF_BSDTIMEVAL && CENV_SYSF_SIGSET + sigset_t allmsk, oldmsk, nomsk; + struct itimerval ntval, vtval; + static ossighandler_t *handler = NULL; + + if (handler != hdlarg) { /* First time with no handler */ + if (hdlarg) + osux_signal(SIGALRM, (handler = hdlarg)); + else { + /* Forced clearing of handler */ + osux_signal(SIGALRM, SIG_IGN); + handler = NULL; + } + } + + sigfillset(&allmsk); /* Specify all signals */ + sigemptyset(&nomsk); /* Specify no signals */ + sigprocmask(SIG_BLOCK, &allmsk, &oldmsk); /* Block them all */ + timerclear(&ntval.it_interval); + timerclear(&ntval.it_value); + setitimer(ITIMER_VIRTUAL, &ntval, &vtval); /* Find & stop virtual timer */ + if (timerisset(&vtval.it_value)) { + /* Some remaining time left to go in virtual timer. + ** Turn it into a real-time one-shot. + */ + ntval.it_value = vtval.it_value; /* No interv, just one-shot */ + setitimer(ITIMER_REAL, &ntval, (struct itimerval *)NULL); + while (!INSBRKTEST()) { + sigsuspend(&nomsk); /* Wait for interrupt */ + } + /* Done, now restore virtual timer appropriately */ + getitimer(ITIMER_REAL, &ntval); + if (timerisset(&ntval.it_value)) { /* If didn't time out */ + vtval.it_value = ntval.it_value; /* get remaining time */ + timerclear(&ntval.it_value); /* Turn off real-time timer */ + setitimer(ITIMER_REAL, &ntval, (struct itimerval *)NULL); + } else + vtval.it_value = vtval.it_interval; /* Full restart */ + } + setitimer(ITIMER_VIRTUAL, &vtval, (struct itimerval *)NULL); + + sigprocmask(SIG_SETMASK, &oldmsk, (sigset_t *)NULL); + +#elif CENV_SYS_MAC + /* Mac does nothing yet to be nice to other processes --Moon */ +#else +# error "Unimplemented OS routine os_v2rt_idle()" +#endif +} + +/* OS_SLEEP - Sleep for N seconds. +** This must not conflict with the behavior of os_timer(). Certain +** systems may require special hackery to achieve this. +** Currently this is only used by dvni20.c, where it is OK to suspend +** all clock interrupts for the duration of the sleep. +** +** On DEC OSF/1 the sleep() call uses a different mechanism independent +** of SIGALRM, which is good. +** +** Solaris sleep() uses ITIMER_REAL and SIGALRM. This is still true +** as of Solaris 5.8. +** +** Perhaps it would be good to move towards using POSIX nanosleep(), +** which claims not to interfere with other timers or signals. +** +** Solaris claims to support nanosleep(): +** in 5.5: -lposix4 +** in 5.8: -lrt +** +** Tru64/FreeBSD/NetBSD/Linux support nanosleep(). +** +** However, before running off to add CENV_SYSF_NANOSLEEP, need to make sure +** this is the right thing; signals, in particular clock interrupts, can +** interrupt the call and make it useless for the purpose: a forced +** suspension of execution for N seconds. +*/ +void +os_sleep(int secs) +{ +#if CENV_SYS_DECOSF + sleep(secs); /* Independent of interval timers! */ + +#elif CENV_SYSF_BSDTIMEVAL + /* Must save & restore ITIMER_REAL & SIGALRM, which conflict w/sleep() */ + ostimer_t savetmr; + + /* Turn off ITIMER_REAL and SIGALRM, saving old state, + do the sleep, then restore the world. + */ + os_timer(OS_ITIMER_REAL, SIG_IGN, 0, &savetmr); + sleep(secs); /* Do the gubbish */ + os_timer_restore(&savetmr); + +#elif CENV_SYS_MAC + /* I don't think the Mac needs this --Moon */ +#else +# error "Unimplemented OS routine os_sleep()" +#endif +} + +#if KLH10_CPU_KS +/* +KS10 Realtime values: + + The KS10 time base is a 71-bit doubleword integer (low sign is +0) that counts at 4.1MHz; i.e. its value increments by 4,100,000 each +second, 4.1 each usec. Each unit is thus 1/4.1 = 0.24390243902439027 usec. + +Comments from KSHACK;KSDEFS: + ; The time is a 71. bit unsigned number. The bottom + ; 12. bits cannot be set. The bottom 2 bits cannot + ; even be read. It increments at 4.1 MHz. The top + ; 59. bits (the ones you can set) thus measure + ; (almost) milliseconds. The top 69. bits (the + ; ones you can read) thus measure "short" + ; microseconds. The time wraps around every 18. + ; million years. To make the top 59. bits actually + ; measure milliseconds, the clock would have to run + ; at 4.096 MHz. However it -really- -does- run at + ; exactly 4.1 MHz! + +A "short microsecond" from the top 69 bits (ignoring the low 2) is: + (1/4.1) * 4 = 0.97560975609756106 usec + +An ITS quantum unit is as close to 4.096 usec as possible. By taking the +top 67 bits (ignoring the low 4) we now have a quantum tick: + (1/4.1) * 16 = 3.9024390243902443 usec + +And this is the derivation of the "3.9 usec tick" in the ITS sources. + +The equation for converting a Unix usec time into KS ticks would be to +compute: + ((tv_sec*1000000)+tv_usec) * 4.1 + + which could be done without floating-point, if one had large enough + integers, as: + (tv_sec * 4100000) + (tv_usec * 4) + (tv_usec / 10) + +For now we'll borrow PDP-10 words and ops to accomplish this. + +*/ + +/* OS_RTM_TOKST - Convert OS realtime ticks into KS ticks +*/ +void +os_rtm_tokst(register osrtm_t *art, + dw10_t *ad) +{ + register dw10_t d; + register w10_t w; + +#if CENV_SYSF_BSDTIMEVAL + LRHSET(w, 017, 0507640); /* 4,100,000. */ + d = op10xmul(w, op10utow(art->tv_sec)); + w = op10utow((int32)((art->tv_usec << 2) + (art->tv_usec / 10))); + op10m_udaddw(d, w); /* Add word into double */ + +#elif CENV_SYS_MAC + unsigned long long value = RTM_PTR_TO_LONG_LONG(art); + value = value * 4 + value / 10; /* microseconds times 4.1 */ + /* Convert 64-bit integer to pdp-10 double-integer format + discarding the sign bit */ + d.w[0].lh = (value >> (35 + 18)) & 0377777; + d.w[0].rh = (value >> 35) & 0777777; + d.w[1].lh = ((value >> 18) & 0377777); + d.w[1].rh = value & 0777777; + +#else +# error "Unimplemented OS routine os_rtm_tokst()" +#endif + *ad = d; +} + + +/* OS_RTM_TOQCT - Convert OS realtime ticks into KS quantum counter ticks (ITS) +** This code assumes it is always given a time interval, not an +** absolute time, and thus 32-bit values should be big enough. +** Note arithmetic is different since we're ignoring the low +** 4 bits of the result; each quantum tick is 16 KS ticks, or +** approx 3.9 usec. +*/ +unsigned long +os_rtm_toqct(register osrtm_t *art) +{ +#if CENV_SYSF_BSDTIMEVAL + return ((unsigned long)(art->tv_sec * 4100000) + + (art->tv_usec << 2) + (art->tv_usec/10)) >> 4; +#elif CENV_SYS_MAC + unsigned long long value = RTM_PTR_TO_LONG_LONG(art); + return (unsigned long)((value * 4 + value / 10) >> 4); +#else +# error "Unimplemented OS routine os_rtm_toqct()" +#endif +} + +#endif /* KLH10_CPU_KS */ + +#if KLH10_CPU_KL + +/* +KL10 Realtime values: + + The KL10 time base hardware counter counts at exactly 1MHz, thus +1 KL tick == 1 usec. + The counter is a 16-bit quantity, thus 32 bits should be quite +sufficient as a return value. The real hardware updates an EPT location +if it overflows, but for our purposes the monitor is always going to +read the time explicitly often enough that overflow should never happen. +See io_rdtime(). + +*/ + +/* OS_RTM_TOKLT - Convert OS realtime ticks into KL ticks (usecs) +*/ +unsigned long +os_rtm_toklt(register osrtm_t *art) +{ +#if CENV_SYSF_BSDTIMEVAL + return ((unsigned long)art->tv_sec * 1000000) + art->tv_usec; +#elif CENV_SYS_MAC + return art->lo; +#else +# error "Unimplemented OS routine os_rtm_toklt()" +#endif +} + +#endif /* KLH10_CPU_KL */ + + +/* Miscellaneous stuff */ + +/* OS_TMGET - Get current real-world time from OS in TM structure. +*/ +int +os_tmget(register struct tm *tm) +{ + time_t tad; + + if (time(&tad) == (time_t)-1) + return 0; +#if 0 /* CENV_SYS_DECOSF Needs libc_r.a instead of libc.a, sigh */ + if (localtime_r(&tad, tm) != 0) + return 0; /* Some problem */ +#else + { + register struct tm *stm; + if (!(stm = localtime(&tad))) + return 0; /* Some problem */ + *tm = *stm; /* Copy static to dynamic stg */ + } +#endif + return 1; +} + +/* Signal handling support, if needed. +** The canonical model for signal handling is BSD, where handlers are +** NOT de-installed when a signal is caught, and system calls are +** restarted when the handler returns. +*/ + +#if CENV_SYS_UNIX + +int +osux_signal(int sig, ossighandler_t *func) +{ + return osux_sigact(sig, func, (ossigact_t *)NULL); +} + +int +osux_sigact(int sig, ossighandler_t *func, ossigact_t *ossa) +{ +#if CENV_SYSF_SIGSET + struct sigaction act; + + act.sa_handler = func; + act.sa_flags = SA_RESTART; + sigemptyset(&act.sa_mask); + sigaddset(&act.sa_mask, sig); /* Suspend this sig during handler */ + if (ossa) + ossa->ossa_sig = sig; + return sigaction(sig, &act, (ossa ? &ossa->ossa_sa : NULL)); +#elif CENV_SYS_BSD + void (*ret)(); + + ret = signal(sig, func); + if (ossa) { + ossa->ossa_sig = sig; + ossa->ossa_handler = func; + } + return (ret == SIG_ERR) ? -1 : 0; +#else +# error "Unimplemented OS routine osux_sigact()" +#endif +} + +int +osux_sigrestore(ossigact_t *ossa) +{ +#if CENV_SYSF_SIGSET + return sigaction(ossa->ossa_sig, + &ossa->ossa_sa, (struct sigaction *)NULL); +#elif CENV_SYS_BSD + return (signal(ossa->ossa_sig, ossa->ossa_handler) == SIG_ERR) + ? -1 : 0; +#else +# error "Unimplemented OS routine osux_sigrestore()" +#endif +} + + +#endif /* CENV_SYS_UNIX */ + +/* Process priority facilities +** +*/ + +int +os_setpriority(ospri_t npri) +{ +#if CENV_SYS_UNIX + if (setpriority(PRIO_PROCESS, 0, npri) == 0) + return TRUE; +#endif + return FALSE; +} + +int +os_getpriority(ospri_t *aopri) +{ +#if CENV_SYS_UNIX + register ospri_t opri; + + errno = 0; + if (((opri = getpriority(PRIO_PROCESS, 0)) != -1) || !errno) { + *aopri = opri; + return TRUE; + } +#endif + return FALSE; +} + +/* Memory Mapping facilities +** These use the SYSV IPC shared memory calls. +** It's becoming increasingly likely that most unices will have them. +** Ugh. Would prefer something like mmap() but it has its own set +** of problems, the most important of which is the fact there is no +** control over pages being (uselessly) written out to disk. +*/ + +int +os_mmcreate(register size_t memsiz, + osmm_t *amm, + char **aptr) +{ +#if CENV_SYS_UNIX && KLH10_DEV_DP + + int shmid; + char *ptr; + + *amm = 0; + *aptr = NULL; + + /* Create a shared mem seg. Set perms to owner-only RW. + ** Note shmget will lose grossly if on a system where its size arg + ** (defined as a u_int) is less than 32 bits! + */ + if ((shmid = shmget(IPC_PRIVATE, (u_int)memsiz, 0600)) == -1) { + fprintf(stderr, "[os_mmcreate: shmget failed for %ld bytes - %s]\n", + (long)memsiz, os_strerror(errno)); + return FALSE; + } + + /* Attempt to attach segment into our address space */ + ptr = (char *)shmat(shmid, (void *)0, SHM_RND); + if ((int)ptr == -1) { + fprintf(stderr, "[os_mmcreate: shmat failed for %ld bytes - %s]\n", + (long)memsiz, os_strerror(errno)); + + /* Clean up by flushing seg */ + shmctl(shmid, IPC_RMID, (struct shmid_ds *)NULL); + return FALSE; + } + + /* Won, return results */ + *amm = shmid; /* Remember shared seg ID */ + *aptr = ptr; + return TRUE; +#else + errno = 0; /* No error, just not implemented */ + return FALSE; +#endif +} + +int +os_mmshare(osmm_t mm, char **aptr) +{ + return 0; /* Nothing for now */ +} + +int +os_mmkill(osmm_t mm, char *ptr) +{ +#if CENV_SYS_UNIX && KLH10_DEV_DP + shmdt((caddr_t)ptr); /* Detach attached segment */ + shmctl(mm, IPC_RMID, /* then try to flush it */ + (struct shmid_ds *)NULL); +#endif + return TRUE; +} + +/* Attempt to lock all of our process memory now and in the future. +*/ +#if CENV_SYS_DECOSF || CENV_SYS_SOLARIS +# include +#endif + +int +os_memlock(int dolock) +{ +#if CENV_SYS_DECOSF || CENV_SYS_SOLARIS + /* Both Solaris and OSF/1 have mlockall() which looks like what we want. + ** It requires being the super-user, but don't bother to check here, + ** just return error if it fails for any reason. + */ + if (dolock) + return (mlockall(MCL_CURRENT+MCL_FUTURE) == 0); + else + return (munlockall() == 0); +#else + return FALSE; +#endif +} + +/* Dynamic/Sharable Library Loading stuff +*/ + +#if CENV_SYS_DECOSF +# include /* Defs for dlopen, dlsym, dlclose, dlerror */ +#endif + +int +os_dlload(FILE *f, /* Report errors here */ + char *path, /* Pathname of loadable lib */ + osdll_t *ahdl, /* Returned handle if any */ + char *isym, /* Init Symbol to look up */ + void **avec) /* Returned symbol address */ +{ +#if CENV_SYS_DECOSF + void *hdl, *vec; + + if (!(hdl = dlopen(path, RTLD_NOW))) { + if (f) + fprintf(f, "Load of \"%s\" failed: %s\n", path, dlerror()); + return FALSE; + } + /* Load won, look up symbol! */ + if (!(vec = dlsym(hdl, isym))) { + if (f) + fprintf(f, "Load of \"%s\" failed, couldn't resolve \"%s\": %s\n", + path, isym, dlerror()); + dlclose(hdl); /* Clean up by flushing it, sigh */ + return FALSE; + } + + /* Everything won, return success! */ + *ahdl = hdl; + *avec = vec; + return TRUE; + +#else /* DLLs not supported */ + if (f) + fprintf(f, "Cannot load \"%s\": Dynamic Libraries not supported\n", + path); + return FALSE; +#endif +} + +int +os_dlunload(FILE *f, osdll_t hdl) +{ +#if CENV_SYS_DECOSF + dlclose(hdl); /* No error return? Foo! */ + return TRUE; /* Hope this worked */ + +#else /* DLLs not supported, never loaded! */ + return TRUE; +#endif +} diff --git a/src/osdsup.h b/src/osdsup.h new file mode 100644 index 0000000..485c38c --- /dev/null +++ b/src/osdsup.h @@ -0,0 +1,257 @@ +/* OSDSUP.H - OS-Dependent Support defs for KLH10 +*/ +/* $Id: osdsup.h,v 2.5 2001/11/19 10:43:28 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: osdsup.h,v $ + * Revision 2.5 2001/11/19 10:43:28 klh + * Add os_rtm_adjust_base for ITS on Mac + * + * Revision 2.4 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#ifndef OSDSUP_INCLUDED +#define OSDSUP_INCLUDED 1 + +#ifdef RCSID + RCSID(osdsup_h,"$Id: osdsup.h,v 2.5 2001/11/19 10:43:28 klh Exp $") +#endif + +#include "word10.h" /* Needed for protos */ + +/* General stuff */ +#if CENV_SYS_MAC + extern int errno; /* This should come from an ANSI file */ +#endif /* MAC */ + +extern void os_init(void); +extern void os_exit(int); +extern char *os_strerror(int); + + +/* General I/O facilities */ +#include +#if CENV_SYS_UNIX +# include /* For open() */ +# include +# include /* For L_SET */ + typedef int osfd_t; /* OS open file descriptor */ + typedef unsigned long osdaddr_t; /* OS disk address */ +# define OS_MAXPATHLEN 512 /* MAXPATHLEN? */ +#elif CENV_SYS_MAC + typedef short osfd_t; /* OS open file descriptor (ioFRefNum) */ + typedef long osdaddr_t; /* OS disk address (ioPosOffset) */ +# define OS_MAXPATHLEN 256 /* Random guess */ +#endif + +extern int os_fdopen(osfd_t *, char *, char *); +extern int os_fdseek(osfd_t, osdaddr_t); +extern int os_fdread(osfd_t, char *, size_t, size_t *); +extern int os_fdwrite(osfd_t, char *, size_t, size_t *); +extern int os_fdclose(osfd_t); + + +/* Compatibility macro definitions */ +#if !(CENV_SYS_UNIX) || CENV_SYS_SOLARIS +# define _setjmp setjmp /* Not everyone has fast version */ +# define _longjmp longjmp +#endif + +/* Signal facilities. Not provided on all environments. +*/ + +#if CENV_SYS_UNIX || CENV_SYS_MAC +# include +#endif + +/* For consistency & convenience, define a "signal handler" function type + that we'll use throughout; code doing anything different will have + to use explicit casts. +*/ +typedef void ossighandler_t(int); + +#ifndef SIG_ERR +# define SIG_ERR ((ossighandler_t *)-1) +#endif + +#if CENV_SYSF_SIGSET +# define ossigset_t sigset_t +# define os_sigemptyset(set) sigemptyset(set) +# define os_sigfillset(set) sigfillset(set) +# define os_sigaddset(set,s) sigaddset(set,s) +# define os_sigdelset(set,s) sigdelset(set,s) +# define os_sigismember(set,s) sigismember(set,s) +# define os_sigsetmask(new,old) sigprocmask(SIG_SETMASK,new,old) +# define os_sigblock(new,old) sigprocmask(SIG_BLOCK,new,old) +#else +# ifndef sigmask +# define sigmask(m) (1L << ((m) - 1)) +# endif +# define ossigset_t unsigned long +# define os_sigemptyset(set) (*(set) = 0) +# define os_sigfillset(set) (*(set) = ~0L) +# define os_sigaddset(set,s) (*(set) |= sigmask(s)) +# define os_sigdelset(set,s) (*(set) &= ~sigmask(s)) +# define os_sigismember(set,s) (*(set) & sigmask(s)) +# define os_sigsetmask(new,old) (((old) ? (*(old) = sigsetmask(*(new))) \ + : sigsetmask(*(new))), 0) +# define os_sigblock(new,old) (((old) ? (*(old) = sigblock(*(new))) \ + : sigblock(*(new))), 0) +#endif + +typedef struct { + int ossa_sig; +#if CENV_SYSF_SIGSET + struct sigaction ossa_sa; +#else + ossighandler_t *ossa_handler; +#endif +} ossigact_t; + +extern int osux_signal(int, ossighandler_t *); /* UNIX only */ +extern int osux_sigact(int, ossighandler_t *, ossigact_t *); +extern int osux_sigrestore(ossigact_t *); + +/* TTY facilities */ +extern void os_ttybkgd(void); /* Say TTY in background mode */ +extern void os_ttyinit(ossighandler_t *rtn); +extern void os_ttysig(ossighandler_t *rtn); +extern void os_ttyreset(void), + os_ttycmdmode(void), os_ttyrunmode(void); +extern int os_ttyintest(void), + os_ttyin(void), + os_ttyout(int), + os_ttysout(char *, int), + os_ttycmchar(void); +extern char *os_ttycmline(char *, int); +extern void os_ttycmforce(void); + + +/* Special event/condition flag macros. +** In order to work in a true threaded environment, these facilities +** will need to have lock/unlock functions added. The flags are cleared +** in only one place in kn10cpu.c, where the flagged actions are handled. +*/ + +typedef int osintf_t; /* Type to use for an interrupt flag */ +extern osintf_t os_swap(osintf_t *, int); +#define INTF_INIT(flag) ((flag) = 0) +#define INTF_SET(flag) ((flag) = 1) +#define INTF_TEST(flag) ((flag) != 0) +#define INTF_ACTBEG(flag) do { (flag) = 2 +#define INTF_ACTEND(flag) } while (os_swap(&(flag), 0) != 2) + + +/* Time facilities */ + +#include /* For os_tmget(), for KL DTE */ + +#if CENV_SYSF_BSDTIMEVAL /* timeval is a BSD artifact */ +# include + typedef struct timeval osrtm_t; +# define OS_RTM_SEC(rtm) ((rtm).tv_sec) +# define OS_RTM_USEC(rtm) ((rtm).tv_usec) +#elif CENV_SYS_MAC + /* An unsigned 64-bit number of microseconds on the Mac */ + typedef UnsignedWide osrtm_t; +# define OS_RTM_SEC(rtm) (os_rtm_to_secs(rtm)) +# define OS_RTM_USEC(rtm) (os_rtm_to_usecs(rtm)) +# define RTM_PTR_TO_LONG_LONG(rtmptr) (*(unsigned long long*)(rtmptr)) +#else + -- ERROR -- +#endif + +typedef struct { + int ostmr_type; +#ifdef ITIMER_REAL +# define OS_ITIMER_REAL ITIMER_REAL +# define OS_ITIMER_VIRT ITIMER_VIRTUAL +#else +# define OS_ITIMER_REAL 0 +# define OS_ITIMER_VIRT 1 +#endif + ossigact_t ostmr_sigact; +#if CENV_SYSF_BSDTIMEVAL + struct itimerval ostmr_itm; +#elif CENV_SYS_MAC + /* XXX: Mac implem uses static interval_timer* timer instead of a + ** field here, fix later. + */ +#else +# error ostmr_itm not implemented +#endif +} ostimer_t; + + +extern int os_vrtmget(osrtm_t *); +extern int os_rtmget(osrtm_t *); +extern void os_rtm_adjust_base(osrtm_t *in, osrtm_t *out, int b_absolute); +extern void os_rtmsub(osrtm_t *, osrtm_t *); +extern void os_rtm_tokst(osrtm_t *, dw10_t *); +extern unsigned long os_rtm_toqct(osrtm_t *); +extern unsigned long os_rtm_toklt(osrtm_t *); + +extern void os_rtimer(ossighandler_t *, uint32); +extern void os_vtimer(ossighandler_t *, uint32); +extern void os_timer(int, ossighandler_t *, uint32, ostimer_t *); +extern void os_timer_restore(ostimer_t *); +extern void os_v2rt_idle(ossighandler_t *); +extern void os_sleep(int); + +extern int os_tmget(struct tm *); /* Only for KL DTE */ + +#if CENV_SYS_MAC +extern unsigned long os_rtm_to_secs(osrtm_t rtm); +extern unsigned long os_rtm_to_usecs(osrtm_t rtm); +#endif + +/* Process priority facilities */ +#if CENV_SYS_UNIX +# include +#endif + +typedef int ospri_t; +extern int os_setpriority(ospri_t); /* Set process priority */ +extern int os_getpriority(ospri_t *); /* Get process priority */ + + +/* Memory Mapping facilities */ +#if CENV_SYS_UNIX +# include +# include +# include +#endif + +typedef int osmm_t; + +extern int os_mmcreate(size_t, osmm_t *, char **); +extern int os_mmshare(osmm_t, char **); +extern int os_mmkill(osmm_t, char *); +extern int os_memlock(int); + +/* Dynamic Library Loading facilities */ +#if CENV_SYS_DECOSF + typedef void *osdll_t; +#else + typedef char *osdll_t; +#endif + +extern int os_dlload(FILE *, char *, osdll_t *, char *, void **); +extern int os_dlunload(FILE *, osdll_t); + +#endif /* ifndef OSDSUP_INCLUDED */ diff --git a/src/prmstr.c b/src/prmstr.c new file mode 100644 index 0000000..63ff057 --- /dev/null +++ b/src/prmstr.c @@ -0,0 +1,707 @@ +/* PRMSTR.C - Parameter & String Parsing support +*/ +/* $Id: prmstr.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1994, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: prmstr.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* These routines are intended to be used by miscellaneous independent +** device programs or modules as well as the KLH10 main command parser, +** hence must avoid any dependencies on other code. +*/ + +#include +#include /* Malloc and friends */ +#include +#include + +#include "rcsid.h" +#include "prmstr.h" + +#ifdef RCSID + RCSID(prmstr_c,"$Id: prmstr.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +static int prmvalscan(char **, int, union prmval *, FILE *); +static int prmvalset(struct prmvar_s *, union prmval *, FILE *, FILE *); +static int s_tol(char *, char **, long *, int); + +static char *prmvtypnam[] = { +# define prmvdef(en, str) str + PRMV_TYPES +# undef prmvdef +}; + + +/* PRM_VARSET - Parse and set one parameter variable according to table. +** Returns TRUE unless parsing error. +** Mungs the input string! +*/ +int prm_varset(char **acp, + struct prmvar_s *tab, + FILE *of, + FILE *ef) +{ + register char *cp = *acp; + int res; + struct prmvar_s *p1, *p2; + struct prmvcx_s px; + + if (!(cp = strchr(cp, '='))) { + if (ef) + fprintf(ef, "Bad syntax for \"%s\", must be =\n", cp); + return FALSE; + } + + *cp++ = '\0'; /* Mung param string, separate name and val */ + + res = s_keylookup(*acp, (voidp_t)tab, sizeof(*p1), + (voidp_t *)&p1, (voidp_t *)&p2); + if (!p1) { + if (ef) + fprintf(ef, "Unknown variable: \"%s\"\n", *acp); + return FALSE; + } + if (p2) { + if (ef) + fprintf(ef, "Ambiguous variable: \"%s\", \"%s\"%s\n", + p1->prmv_name, p2->prmv_name, res > 2 ? ", ..." : ""); + return FALSE; + } + + *acp = cp; + if (!prmvalscan(acp, p1->prmv_typf, &px.prmvcx_val, ef)) { + if (ef) + fprintf(ef, "Bad syntax for \"%s\", expecting %s value: \"%s\"?\n", + p1->prmv_name, prmvtypnam[p1->prmv_typf & PRMVF_TYPE], cp); + return FALSE; + } + + /* Value parsed into temporary holder, now set up rest of context and + invoke appropriate set function! + */ + px.prmvcx_var = p1; /* Note variable */ + px.prmvcx_str = cp; /* and original parameter string */ + px.prmvcx_of = of; /* Set up I/O in context */ + if (!(px.prmvcx_ef = ef)) + px.prmvcx_ef = of; + + if (p1->prmv_set) { /* Has its own set function? */ + return (*(p1->prmv_set))(&px); /* Do it! */ + } + return prmvp_set(&px); /* Do standard param set */ +} + +/* PRM_VARLINESET - Parse and set entire line of variables/parameters +** Returns TRUE unless parsing error +** Mungs the input string! +*/ +int prm_varlineset(char *s, + struct prmvar_s *tab, + FILE *of, + FILE *ef) +{ +#define NTOKS 100 + char tokbuf[300+NTOKS]; /* Big work buffer */ + char *tokarr[NTOKS]; /* Lots of tokens */ + char **tkp; + char *tcp = tokbuf; + size_t tcnt = sizeof(tokbuf); + size_t slen = strlen(s); + int n; + + n = s_tokenize(tokarr, NTOKS-1, &tcp, &tcnt, &s, &slen); + tokarr[n] = NULL; + for (tkp = tokarr; *tkp; ++tkp) { + if (!prm_varset(tkp, tab, of, ef)) { + if (ef && tkp[1]) { /* Error? See if more left */ + fprintf(ef, "Parsing aborted.\n"); + } + return FALSE; + } + } + return TRUE; +#undef NTOKS +} + + +/* PRMVALSCAN - Parse a value, given type to expect. +** Not yet completely consistent with respect to behavior of +** updated CP - still expects pre-tokenized input ending with NUL. +** "ef" is not really used at the moment but could be later. +*/ +static int +prmvalscan(char **acp, + int typf, + union prmval *vp, + FILE *ef) +{ + register char *cp = *acp; + char *endp; + long ltmp; + int ret; + + if (!cp) return 0; + switch (typf & PRMVF_TYPE) { + case PRMVT_BOO: + vp->vi = (int)strtol(cp, &endp, 10); /* Try as number */ + if (cp != endp) { + *acp = endp; + if (*endp == '\0') /* See if gobbled entire string */ + return TRUE; + return FALSE; /* Looked like number but wasn't? */ + } + /* Later do full keyword scan - for now use ugliness */ + else if (s_match(cp, "on" )==2) vp->vi = TRUE; + else if (s_match(cp, "of" )==2) vp->vi = 0; + else if (s_match(cp, "off")==2) vp->vi = 0; + else { + return FALSE; /* Not a known boolean keyword */ + } + *acp += strlen(cp); + return TRUE; + case PRMVT_DEC: + vp->vi = (int)strtol(cp, &endp, 10); + if (cp == endp) { + return FALSE; /* Not a number at all */ + } + if (*endp == '.') ++endp; + *acp = endp; + if (*endp != '\0') { + return FALSE; /* Only partially a number */ + } + return TRUE; + case PRMVT_OCT: + ret = s_tonum(cp, <mp); + if (ret) vp->vi = (int)ltmp; + return ret; + case PRMVT_WRD: + ret = s_towd(cp, &vp->vw); + return ret; + case PRMVT_STR: + vp->vs = cp; + *acp += strlen(cp); + return TRUE; + } + return FALSE; /* Unknown value type */ +} + +/* Standard variable set, given context + */ +int +prmvp_set(struct prmvcx_s *px) +{ + struct prmvar_s *p = px->prmvcx_var; + union prmval *vp = &px->prmvcx_val; + FILE *of = px->prmvcx_of; + FILE *ef = px->prmvcx_ef; + + if (of) + fprintf(of," %s: ", p->prmv_name); + switch (p->prmv_typf & PRMVF_TYPE) { + case PRMVT_BOO: + printf("%s => %s\n", *(int *)(p->prmv_loc) ? "On" : "Off", + vp->vi ? "On" : "Off"); + *(int *)(p->prmv_loc) = vp->vi; + break; + case PRMVT_OCT: + printf("%#.0o => %#.0o\n", *(int *)(p->prmv_loc), vp->vi); + *(int *)(p->prmv_loc) = vp->vi; + break; + case PRMVT_DEC: + printf("%d. => %d.\n", *(int *)(p->prmv_loc), vp->vi); + *(int *)(p->prmv_loc) = vp->vi; + break; + case PRMVT_WRD: + printf("%lo,,%lo => %lo,,%lo\n", + (long)LHGET(*(w10_t *)(p->prmv_loc)), + (long)RHGET(*(w10_t *)(p->prmv_loc)), + (long)LHGET(vp->vw), (long)RHGET(vp->vw)); + *(w10_t *)(p->prmv_loc) = vp->vw; + break; + case PRMVT_STR: + if (*(char **)(p->prmv_loc)) printf("\"%s\"", *(char **)(p->prmv_loc)); + else printf("NULL"); + if (vp->vs) printf(" => \"%s\"\n", vp->vs); + else printf("NULL\n"); + + { /* Ensure have new string before flushing old one! */ + char *tmp = NULL; + + if (vp->vs && !(tmp = s_dup(vp->vs))) { + printf(" Error: malloc failed for new string, var not set!\n"); + return FALSE; + } + /* OK, free up old if necessary */ + if ((p->prmv_typf & PRMVF_DYNS) && *(char **)(p->prmv_loc)) + free(*(char **)(p->prmv_loc)); + *(char **)(p->prmv_loc) = tmp; + } + p->prmv_typf |= PRMVF_DYNS; + break; + } + + return TRUE; +} + + +int +prm_varshow(register struct prmvar_s *p, + FILE *of) +{ + int res = TRUE; + + fprintf(of, " %-10s= ", p->prmv_name); + switch (p->prmv_typf & PRMVF_TYPE) { + case PRMVT_BOO: + fprintf(of, "%s", *(int *)p->prmv_loc ? "On" : "Off"); + break; + case PRMVT_DEC: + fprintf(of, "%d.", *(int *)p->prmv_loc); + break; + case PRMVT_OCT: + fprintf(of, ((*(int *)p->prmv_loc) ? "%#.0o" : "0"), + *(int *)p->prmv_loc); + break; + case PRMVT_STR: + fprintf(of, "\"%s\"", *(char **)p->prmv_loc); + break; + case PRMVT_WRD: + { + w10_t w = *(w10_t *)p->prmv_loc; + + if (LHGET(w)) fprintf(of, "%lo,,", (long)LHGET(w)); + fprintf(of, "%lo", (long)RHGET(w)); + } + break; + default: + fprintf(of,"", p->prmv_typf); + res = FALSE; + } + if (p->prmv_help) + fprintf(of, " \t%s", p->prmv_help); + fprintf(of, "\n"); + return res; +} + +#if 0 + +static char * +wdscan(char **acp) +{ + register char *cp = *acp, *s; + + if (isspace(*cp)) /* Skip to word */ + while (isspace(*++cp)); + s = cp; + switch (*s) { + case 0: return NULL; /* Nothing left */ + default: /* Normal word */ + while (!isspace(*++s) && *s); + if (*s) *s++ = 0; /* Tie off word */ + break; + } + *acp = s; + return cp; +} + +#endif + +/* Like strtol() but allows trailing '.' to force decimal. +** base 8 - default is octal unless '.' trails number +** base 10 - default is decimal +*/ +static int +s_tol(register char *cp, + register char **aep, + long *ares, + int base) +{ + register char *s; + int sign = 1; + + if (!cp || !*cp) { + *aep = cp; + return 0; + } + if (*cp == '-') { + sign = -1; + ++cp; + } + if (*cp == '0' && (cp[1] == 'x' || cp[1] == 'X')) { + *ares = strtol(cp, aep, 16) * sign; + return 1; + } + for (s = cp; isdigit(*s); ++s); /* Skip all digits */ + if (*s == '.') { + *ares = strtol(cp, aep, 10) * sign; /* Force decimal! */ + if (aep && *aep == s) + (*aep)++; /* Skip over point */ + return 1; + + } + + /* Nothing explicit, use default. */ + *ares = strtol(cp, aep, base) * sign; + return 1; +} + +int +s_tonum(char *cp, + long *aloc) +{ + *aloc = 0; + if (!s_tol(cp, &cp, aloc, 8)) /* Default is octal */ + return 0; + return *cp ? 0 : 1; /* Fail if anything left in string */ +} + +int +s_todnum(char *cp, + long *aloc) +{ + *aloc = 0; + if (!s_tol(cp, &cp, aloc, 10)) /* Default is decimal */ + return 0; + return *cp ? 0 : 1; /* Fail if anything left in string */ +} + + +/* S_TOWD - Returns 0 if failed, 1 if one value, 2 if two values +** interpreted as 2 halfwords. +*/ +int +s_towd(char *str, + w10_t *wp) +{ + long num; /* Known to be at least 32 bits */ + register char *cp; + + if (cp = strchr(str, ',')) { + *cp++ = 0; + if (*cp == ',') cp++; + if (s_tonum(str, &num)) + LHSET(*wp, num & H10MASK); + else return 0; + if (s_tonum(cp, &num)) + RHSET(*wp, num & H10MASK); + else return 0; + return 2; + + } else if (s_tonum(str, &num)) { + LHSET(*wp, (num>>18)&H10MASK); + RHSET(*wp, num&H10MASK); + return 1; + } + return 0; +} + +static S_KEYSBEGIN(prmbools) + S_KEYDEF("on", (char *)1) + S_KEYDEF("off", (char *)0) + S_KEYDEF("yes", (char *)1) + S_KEYDEF("no", (char *)0) + S_KEYDEF("true",(char *)1) + S_KEYDEF("false",(char *)0) +S_KEYSEND + +int +s_tobool(register char *cp, + int *ip) +{ + int kix; + char *endp; + + *ip = (int)strtol(cp, &endp, 10); /* Try as number */ + if (cp != endp) { + if (*endp == '\0') /* See if gobbled entire string */ + return TRUE; + return FALSE; /* Looked like number but wasn't? */ + } + /* Not number, do full keyword scan */ + if (s_xkeylookup(cp, prmbools, sizeof(prmbools[0]), + (voidp_t *)NULL, (voidp_t *)NULL, + &kix, (int *)NULL) == 1) { + *ip = (prmbools[kix].prmk_p ? 1 : 0); + return TRUE; + } + return FALSE; /* Not a known boolean keyword */ +} + +/* PRM_NEXT - Routine to parse next parameter name or name=value pair +** from input string. +** = ... +*/ +int +prm_next(register struct prmstate_s *p) +{ + register char *cp; + char *bufp = p->prm_bcp; /* Use temps so originals not */ + size_t bufcnt = p->prm_bln-1; /* affected by call to s_1token */ + + /* First get next token, update input string pointers */ + p->prm_name = cp = s_1token(&bufp, &bufcnt, + &p->prm_icp, &p->prm_iln); + + if (!cp) + return PRMK_DONE; /* No more tokens, input done! */ + + /* Split off value, if any */ + if (p->prm_val = strchr(cp, '=')) + *(p->prm_val)++ = '\0'; + + /* Look up name in keyword table */ + switch (p->prm_nhits = s_xkeylookup(cp, p->prm_keytab, p->prm_keysiz, + (voidp_t *)(&p->prm_key), (voidp_t *)(&p->prm_key2), + &p->prm_idx, &p->prm_idx2)) { + case 0: + /* Lookup failed, pass error down */ + return PRMK_NONE; /* Set: name, val */ + + case 1: + /* Success, just one match, pass on parameter value if one */ + return p->prm_idx; /* Set: name, val, key, idx */ + + default: + /* Ambiguous, pass on parameter keyword. */ + return PRMK_AMBI; /* Set: name, val, key, key2, idx, idx2 */ + } +} + +/* S_XKEYLOOKUP - core table lookup function. Works for any table + * where the first element of each entry is a keyword char pointer. + */ +int +s_xkeylookup(char *cp, /* Keyword to look up */ + register voidp_t keytab, /* Keyword table */ + register size_t keysiz, /* Size of entry in table */ + voidp_t *key1, + voidp_t *key2, /* First 2 returned keys */ + int *kix1, + int *kix2) /* Indices of these keys */ +{ + register voidp_t k1, k2; + register char *ts; + register int kidx, kx1, kx2; + register int pmatches = 0; + + k1 = k2 = NULL; + kx1 = kx2 = PRMK_NONE; + if (*cp) { + for (kidx = 0; ts = ((struct prmkey_s *)keytab)->prmk_key; + kidx++, + keytab = (voidp_t)(((char *)keytab) + keysiz)) { + switch (s_match(cp, ts)) { + case 1: /* Matched partially, continue but keep first two */ + pmatches++; + if (!k1) k1 = keytab, kx1 = kidx; + else if (!k2) k2 = keytab, kx2 = kidx; + break; + case 2: /* Matched exactly, win now */ + if (key1) *key1 = keytab; + if (kix1) *kix1 = kidx; + if (key2) *key2 = NULL; + if (kix2) *kix2 = PRMK_NONE; + return 1; + } + } + } + /* No exact match, return results of full scan */ + if (key1) *key1 = k1; + if (kix1) *kix1 = kx1; + if (key2) *key2 = k2; + if (kix2) *kix2 = kx2; + return pmatches; +} + + +int +s_keylookup(char *cp, /* Keyword to look up */ + register void *keytab, /* Keyword table */ + register size_t keysiz, /* Size of entry in table */ + void **k1, void **k2) /* First 2 returned keys */ +{ + return s_xkeylookup(cp, keytab, keysiz, k1, k2, (int *)NULL, (int *)NULL); +} + +void * +s_fkeylookup(char *cp, /* Keyword to look up */ + register void *keytab, /* Keyword table */ + register size_t keysiz) /* Size of entry in table */ +{ + voidp_t key1; + + return ((1 == s_xkeylookup(cp, keytab, keysiz, &key1, (voidp_t *)NULL, + (int *)NULL, (int *)NULL)) + ? key1 : NULL); +} + +/* Tokenize a command string. +** Copies tokens into work buffer, up to given number. +** Returns # of tokens parsed. +*/ +int +s_tokenize(char **arr, + int arrlen, + char **tcp, + size_t *tcnt, + char **fcp, + size_t *fcnt) +{ + register int n = 0; + char *cp; + + while (n < arrlen && *tcnt && (cp = s_1token(tcp, tcnt, fcp, fcnt))) { + arr[n++] = cp; + } + return n; +} + +/* Parse off a token. +** Updates all args. +** source pointer/count = last char read. May be NUL. +** dest pointer/count = last char written (NUL always follows it). +** dest count = 0 if token was truncated. +** Returns NULL if no token found. +*/ +char * +s_1token(char **tcp, + size_t *tcnt, + char **fcp, + size_t *fcnt) +{ + register char *t, *f = *fcp; + register size_t tct, fct = *fcnt; + + if (isspace(*f)) { /* Skip to word */ + do --fct; + while (isspace(*++f)); + } + if (!*f) { + *fcp = f; + *fcnt = fct; + return NULL; + } + + t = *tcp; + tct = *tcnt; + for (; *f && fct; --fct, f++) { + switch (*f) { + + /* Break chars should go here. Maybe pass breakstring? */ + case ' ': /* Whitespace becomes break */ + case '\t': /* Will never be first thing in token */ + + case '\n': + case '\r': + if (t == *tcp) { /* If first char of token, keep it */ + /* Nothing yet, so make break char be single-char token */ + if (tct > 1) --tct, *t++ = *f; + else tct = 0; + } + break; + + /* Quote char? */ + case PRMQUOTCHAR: + ++f; /* Point to next char */ + if (--fct <= 0) /* Ugh, none left, store nothing */ + break; + /* Drop thru to unconditionally store quoted char */ + + default: + if (tct > 1) --tct, *t++ = *f; /* Add char if room */ + else tct = 0; + continue; /* Get another */ + } + break; /* Break from switch is break from loop */ + } + + /* OK, tie off token. Will always write at least NUL char if + ** there was even 1 char of room in the to-buffer, since the + ** accumulation code above is careful to stop before the last char. + */ + if (*tcnt > 0) { + *t++ = '\0'; /* Write terminating NUL */ + if (tct > 0) + --tct; + } + *fcp = f; /* Update from-buffer */ + *fcnt = fct; + f = *tcp; /* Remember start of token, for return val */ + *tcp = t; /* Update to-buffer */ + *tcnt = tct; + return f; +} + + +/* S_EZTOKEN - Like s_1token but simpler to use. +** Updates only the source pointer; assumes nul-terminated. +** Returns # of chars in token; 0 if all gone. +*/ +size_t +s_eztoken(char *tcp, + size_t tcnt, + char **fcp) +{ + size_t otcnt = tcnt; + size_t fcnt = *fcp ? strlen(*fcp) : 0; + + if (s_1token(&tcp, &tcnt, fcp, &fcnt)) + return otcnt - tcnt; + return 0; +} + +/* S_MATCH - compare strings, ignoring case. +** Returns 0 if mismatched +** 1 if S1 is initial substring of S2 +** 2 if S1 is exact match of S2 +*/ +int +s_match(register char *s1, + register char *s2) +{ + register int c1, c2; + + while (c1 = *s1++) { + if (isupper(c1)) /* Must test with isupper cuz some */ + c1 = tolower(c1); /* implementations fuck up otherwise */ + if (isupper(c2 = *s2++)) + c2 = tolower(c2); + if (c1 != c2) + return 0; + } + return *s2 ? 1 : 2; +} + + +/* S_DUP - Duplicate string, using malloc +** Returns NULL if alloc failed +*/ +char * +s_dup(register char *s) +{ + register char *cp; + + if (!s) + return NULL; + if (cp = (char *)malloc(strlen(s)+1)) + strcpy(cp, s); + return cp; +} diff --git a/src/prmstr.h b/src/prmstr.h new file mode 100644 index 0000000..b5fc4ab --- /dev/null +++ b/src/prmstr.h @@ -0,0 +1,202 @@ +/* PRMSTR.H - Parameter & String Parsing support definitions +*/ +/* $Id: prmstr.h,v 2.4 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1994, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: prmstr.h,v $ + * Revision 2.4 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#ifndef PRMSTR_INCLUDED +#define PRMSTR_INCLUDED 1 + +#ifdef RCSID + RCSID(prmstr_h,"$Id: prmstr.h,v 2.4 2001/11/10 21:28:59 klh Exp $") +#endif + +#include "word10.h" + +/* Canonical C true/false values */ +#ifndef TRUE +# define TRUE 1 +#endif +#ifndef FALSE +# define FALSE 0 +#endif + +/* Commonly needed type (at one time "void" was unknown) */ +typedef void *voidp_t; + +#define PRMQUOTCHAR '\\' + +/* Interaction-oriented variable=value parsing stuff */ +/* Primarily for use by main command parser */ + +union prmval { + int vi; + long vl; + char *vs; + w10_t vw; +}; + +struct prmvar_s; /* Forward tag */ +struct prmvcx_s { /* Parameter variable context */ + struct prmvar_s *prmvcx_var; /* Pointer to var being processed */ + union prmval prmvcx_val; /* Current value */ + char *prmvcx_str; /* Actual parameter string */ + FILE *prmvcx_of; /* Normal output if any */ + FILE *prmvcx_ef; /* Error output if any */ +}; + +#define PRMVAR(name, help, typf, loc, set, sho) \ + { name, help, typf, (voidp_t)(loc), set, sho } + +struct prmvar_s { + char *prmv_name; /* Variable name */ + char *prmv_help; /* Minimal help text */ + int prmv_typf; /* Type and flags */ + voidp_t prmv_loc; /* Generic pointer */ + int (*prmv_set)(struct prmvcx_s *); + int (*prmv_sho)(struct prmvcx_s *); +}; + +#define PRMV_TYPES \ + prmvdef(PRMVT_NULL=0, "NULL"), \ + prmvdef(PRMVT_BOO, "boolean"), \ + prmvdef(PRMVT_DEC, "decimal"), \ + prmvdef(PRMVT_OCT, "octal"), \ + prmvdef(PRMVT_STR, "string"), \ + prmvdef(PRMVT_WRD, "10word"), \ + prmvdef(PRMVT_6, ""), \ + prmvdef(PRMVT_7, ""), \ + prmvdef(PRMVT_N, NULL) + +enum { /* Define enums for variable types */ +# define prmvdef(i,s) i + PRMV_TYPES +# undef prmvdef +}; + +#define PRMVF_TYPE 07 /* Mask for type number (PRMVT_ value) */ +#define PRMVF_DYNS 0100 /* String is dynamically allocated */ + /* (This could be problematical if there + ** are aliases for a DYN var's name, since + ** the flag is updated in only one place) + ** (Could invent PRMVT_ALIAS w/ptr?) + */ + +extern int prm_varset(char **, struct prmvar_s *, FILE *, FILE *); +extern int prm_varlineset(char *, struct prmvar_s *, FILE *, FILE *); +extern int prm_varshow(struct prmvar_s *, FILE *); +extern int prmvp_set(struct prmvcx_s *); + + +/* Key lookup stuff */ + +struct prmkey_s { + char *prmk_key; + char *prmk_p; /* Actually a (void *) */ +}; + +#define S_KEYSBEGIN(name) struct prmkey_s name[] = { +#define S_KEYDEF(key,nod) { key, (char *)(nod) }, +#define S_KEYSEND { 0, 0 } }; + +extern void *s_fkeylookup(char *cp, voidp_t tab, size_t entsiz); +extern int s_keylookup (char *cp, voidp_t tab, size_t entsiz, + voidp_t *k1, voidp_t *k2); +extern int s_xkeylookup(char *cp, voidp_t tab, size_t entsiz, + voidp_t *k1, voidp_t *k2, int *x1, int *x2); + +/* Random useful string stuff */ + +extern int s_tokenize(char **, int, char **, size_t *, char **, size_t *); + +extern char *s_1token(char **, size_t *, char **, size_t *); +extern size_t s_eztoken(char *, size_t , char **); + +extern int s_tobool(char *, int *); +extern int s_tonum(char *, long *); +extern int s_todnum(char *, long *); +extern int s_towd(char *, w10_t *); +extern char *s_dup(char *); +extern int s_match(char *, char *); + +/* New parameter parsing stuff */ + +/* Iterative scheme to process string of the form + = ... + + foo() + { + struct prmstate_s prm; + char workbuff[]; + ... + prm_init(&prm, workbuff, sizeof(workbuff), + inpstr, strlen(inpstr), + keytab, keysiz); + ... + for (;;) switch (prm_next(&prm)) { + case PRMK_DONE: + case PRMK_NONE: // prm_name & prm_val set + case PRMK_AMBI: // Ditto, plus prm_matches, prm_idx, prm_idx2 + case : // Specific unique match case + default: // Handle matches not supported + } + } +*/ + + + + +#define PRMK_NONE (-1) /* No match */ +#define PRMK_DONE (-2) /* Input string done */ +#define PRMK_AMBI (-3) /* Token ambiguous */ + +struct prmstate_s { + /* Vars that must be initialized */ + char *prm_icp; /* Input string */ + size_t prm_iln; /* Input length */ + char *prm_bcp; /* Buffer pointer */ + size_t prm_bln; /* Buffer length */ + size_t prm_keysiz; /* Size of each keyword table entry */ + struct prmkey_s * + prm_keytab; /* Pointer to keyword table */ + + /* Vars returned by each call to prm_next() */ + char *prm_name; /* Parameter name if one */ + char *prm_val; /* Parameter value if one */ + int prm_nhits; /* # of matches */ + int prm_idx; /* Index of 1st match */ + int prm_idx2; /* Index of 2nd match */ + struct prmkey_s * + prm_key, /* Pointers to matching entries */ + prm_key2; +}; + +#define prm_init(prm, buff, blen, inpstr, inplen, keytab, keysiz) ( \ + (prm)->prm_bcp = (buff), \ + (prm)->prm_bln = (blen), \ + (prm)->prm_icp = (inpstr), \ + (prm)->prm_iln = (inplen), \ + (prm)->prm_keytab = (struct prmkey_s *)(keytab), \ + (prm)->prm_keysiz = (keysiz) ) + +extern int prm_next(struct prmstate_s *); + +#endif /* ifndef PRMSTR_INCLUDED */ diff --git a/src/rcsid.h b/src/rcsid.h new file mode 100644 index 0000000..ef2f359 --- /dev/null +++ b/src/rcsid.h @@ -0,0 +1,63 @@ +/* RCSID.H - Standardize handling of RCS ident strings +*/ +/* $Id: rcsid.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: rcsid.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* + The main reason for defining the RCSID macro is to allow better +control of whether, and how, source control ID strings are included in +the binary output. The use of a macro also allows identifier +concatenation which in turn allows this mechanism to work for include +files as well, as long as they follow these conventions: + + - RCSID should be invoked only once (ie within _INCLUDED code). + - The module name for "foo.h" should be "foo_h". + - To be safe, use of RCSID should be within an #ifdef RCSID + and only put where it is safe to define data. + +This works because the _INCLUDED ensures there are no duplicate defs +within the current .C module, and the defs that are made will not +conflict with inclusions in other modules because of the "static" +storage class. + +Another mechanism needs to be used for special cases such as include +files that are meant to be included multiple times, eg for generating +tables. + +*/ + +#ifndef RCSID_INCLUDED +#define RCSID_INCLUDED 1 + +#ifndef RCSID +# ifndef lint +# define RCSID(mod,idstr) static const char rcsid_ ## mod [] = idstr; +# else +# define RCSID(mod,idstr) +# endif +#endif + +#ifdef RCSID + RCSID(rcsid_h,"$Id: rcsid.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +#endif /* ifndef RCSID_INCLUDED */ diff --git a/src/tapedd.c b/src/tapedd.c new file mode 100644 index 0000000..6fa81f7 --- /dev/null +++ b/src/tapedd.c @@ -0,0 +1,1564 @@ +/* TAPEDD.C - Utility to copy tapes +*/ +/* $Id: tapedd.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: tapedd.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* TAPEDD is mainly used to copy tapes onto disk and make +** further tape copies, either directly from the original tapes +** or from the disk version. It was previously called TDCOPY; +** TAPEDD is its new, portable incarnation AND HAS NOT BEEN +** TESTED very much yet. Be careful! +** +** In order to represent the disk version accurately, a "tape directory" +** is used to describe the format of the raw data, including record +** lengths and tapemarks. This permits anything that understands the +** tape directory to read the raw-data file as if it were an actual tape. +** +** In the absence of properly implemented pseudo-devices on Unix, +** the next step would be to provide a library-type interface such that +** the routines can be applied either to on-disk files or actual tapes, +** without needing to know which is which. VMTAPE.C is a step towards +** this; OSDTAP.C could complete it. +*/ + +#include +#include +#include +#include +#include /* exit() */ +#include +#include /* For error-reporting functions */ +#include /* For open() flags */ + +#include "rcsid.h" +#include "cenv.h" +#include "vmtape.h" /* Include virtual magtape stuff */ + +#if CENV_SYS_T20 +# include +# include /* FLD macros */ +# define char8 _KCCtype_char8 +# define CENV_SYSF_STRERROR 1 +# define NULLDEV "NUL:" +# define FD_STDIN 0 +# define FD_STDOUT 1 + +#elif CENV_SYS_UNIX +# include /* Basic Unix syscalls */ +# include +# include +# include +# define char8 unsigned char +# define O_BSIZE_8 0 +# define NULLDEV "/dev/null" +# define FD_STDIN 0 +# define FD_STDOUT 1 +# define strCMP strcmp /* Temporary compat hack */ +#endif + + +#define MAXRECSIZE (1L<<16) /* was ((15*518*5)+512) */ +#define FNAMSIZ 200 + +#define TRUE 1 +#define FALSE 0 + +#ifdef RCSID + RCSID(tapedd_c,"$Id: tapedd.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* For now, must include VMT source directly, so as to avoid compile-time +** switch conflicts (eg with KLH10). +*/ +#include "vmtape.c" + +/* New TAPEDD parameters */ + +char nusage[] = "\ +Usage: tapedd \n\ + itX= (Required) Input Tape device, where 'X' is optional\n\ + drive spec: (defaults to 'h')\n\ + h - Half-inch magtape drive (default)\n\ + q - QIC (quarter-inch cartridge) drive\n\ + 8 - 8mm drive\n\ + 4 - 4mm DAT drive\n\ + vF - Virtual tape & drive, where 'F' is optional\n\ + format spec: (defaults based on file extension)\n\ + r - Raw format, paired with control file\n\ + s - TPS format\n\ + e - TPE format\n\ + c - TPC format\n" +#if VMTAPE_ITSDUMP +" i - (read-only) ITS DUMP tapedir\n" +#endif +#if VMTAPE_T20DUMP +" 6 - (read-only) TOPS-10/20 DUMPER V6 tapedir\n" +#endif +"\ + otX= (Required) Output Tape device, X as above\n\ + {i,o}c= alternate tape Control file (old id=,od=)\n\ + {i,o}f= alternate raw data file\n\ + {i,o,}bs= Block size (record length)\n\ + log= Log filespec (defaults to stderr)\n\ + rskip=<#> Skip # input records\n\ + fskip=<#> Skip # input files/tapemarks\n\ + rcnt=<#> Max # records to write\n\ + fcnt=<#> Max # files/tapemarks to write\n\ + peot Use physical EOT, ignore logical EOT (double tapemark)\n\ + test Parse control file, output result to stdout\n\ + verbose Verbose\n\ +"; + + +/* Elaboration on new TAPEDD parameter switches: + +--REQUIRED-- + {i,o}t*= Tape path spec (implies both control & data) +--OPTIONAL-- + {i,o}f= Tape data spec + {i,o}c= Tape control (description) spec + +Defaults: + If a device is specified (one of 4/8/q/h), no control file is used + unless one is specified with {i/o}d. + An {i/o}f spec is redundant but will change the path without + changing the device type. + If a virtual tape is specified (v), both the control file and + the data file pathnames are derived from that spec. + Either derivation may be overriden with the appropriate + additional spec ({i/o}f or {i/o}d). +Possible special cases: + If no ot*= spec is given, data output is assumed to be the null device. + This is mainly useful when generating a tape-desc file from + the input; od= can specify where it goes. + of= is ignored. + If no it*= spec is given, no data is input, although a tape-desc + file can be given with id=. + if= is ignored. + + Thus, the equivalent of "test" is: + tapedd id=foo od= + +Whenever a null filename is specified (eg with "of="), it is understood + to mean standard input or output (depending on whether the spec + was for input or output). + +More special cases (the user is not expected to know this, but in case +any of them are tried, this table is an attempt to figure out what +would be logical behavior). Note "itD=" means "it{h,q,8,4}" as opposed +to "itv=". + +COMMAND-LINE DEV + SPECS STRUCT INTERPRETATION +it*= id= if= p d r + - - - - - - Error: No input of any kind (null tape) + - - x - - - Error, ambiguous (later can test or make assumptions) + - tdr - - d - Error unless also have od= (testing tapedesc parse) + - tdr x - - - Error, ambiguous (later can test or make assumptions) +itD=T - - T - T OK: Reads device: type D, dev T. +itD=T - xdv T - x OK: Reads device: type D, dev x. (T ignored) +itD=T tdr - T d T OK: Reads device: type D, dev T, checks vs dir d [*] +itD=T tdr xdv T d x OK: Reads device: type D, dev x, checks vs dir d [*] +itv=V - - V Vd Vr OK: Reads virtual: dir Vd, raw Vr. +itv=V - raw V Vd r OK: Reads virtual: dir Vd, raw r. +itv=V tdr - V d Vr OK: Reads virtual: dir d, raw Vr. +itv=V tdr raw V d r OK: Reads virtual: dir d, raw r. + + [*] = Reads records of sizes and numbers + given by tapedir; warns if any clashes? + Or scan normally, cross-check vs tapedir. + +ot*= od= of= p d r + - - - - - - Error: No output of any kind (useless) + - - x - - - Error, ambiguous? (or test or assume 1/2"?) + - tdr - - d - OK: just outputs tapedir. + - tdr x - - - Error, ambiguous? (or test or assume 1/2"?) +otD=T - - T - T OK: Writes device: type D, dev T. +otD=T - xdv T - x OK: Writes device: type D, dev x. (T ignored) +otD=T tdr - T d T OK: Writes device: type D, dev T, plus tapedir d +otD=T tdr xdv T d x OK: Writes device: type D, dev x, plus tapedir d +otv=V - - V Vd Vr OK: Writes virtual: dir Vd, raw Vr. +otv=V - raw V Vd r OK: Writes virtual: dir Vd, raw r. +otv=V tdr - V d Vr OK: Writes virtual: dir d, raw Vr. +otv=V tdr raw V d r OK: Writes virtual: dir d, raw r. + + +*/ +#if 0 /* Old TDCOPY switches, for posterity */ +char usage[] = "\ +Usage: tdcopy \n\ + -r Read tape only (make tapedir, no copy)\n\ + -w Write tape (from tapedir)\n\ + -c Copy tapes directly\n\ + -t Test tapedir (parse tapedir, rewrite to stdout)\n\ + -i Input filespec (- is stdin)\n\ + -o Output filespec (- is stdout)\n\ + -l Log filespec (required if -i or -o use -)\n\ + -q Previous -i or -o filespec is QIC drive\n\ + -h Previous -i or -o filespec is 1/2\" drive\n\ + -8 Previous -i or -o filespec is 8mm drive\n\ + -b <#> Block (record) size to use (esp for QIC)\n\ + -v Verbose\n\ + -n <#> # of records to do\n\ + -m <#> # of files (tapemarks) to do\n\ + -p Use physical EOT (ignore logical)\n\ +"; +#endif /* 0 */ + + +typedef unsigned long rsiz_t; /* Type to hold size of record */ + +/* Switch parameters */ +int sw_tdtest = FALSE; +int sw_peot = FALSE; +int sw_verbose = FALSE; +int sw_maxrec = 0; +int sw_maxfile = 0; +int sw_recskip = 0; +int sw_fileskip = 0; +rsiz_t sw_bothsiz = 0; +char *sw_logpath = NULL; +FILE *logf = NULL; + +struct dev { + char *d_pname; /* Print name, "In" or "Out" */ + char *d_path; /* Tape drive path spec */ + char *d_cpath; /* Control (descriptor) path spec */ + char *d_rpath; /* Raw data path spec */ + int d_istape; /* Tape device (hardware or virtual) */ + int d_vfmt; /* If virtual, tape format to use */ + rsiz_t d_recsiz; /* Block (record) size to use */ + char8 *d_buff, *d_iop; /* Ptr to buffer loc, ptr into buffer */ + rsiz_t d_blen, d_buse; /* Actual buffer length, # chars used */ + int d_bisdyn; /* True if buff dynamically allocated */ + struct vmtape d_vmt; /* Virtual magtape info */ + + /* Hardware tape info */ + int d_fd; + int + mta_herr, /* Hard errors (unrecoverable) */ + mta_serr, /* Soft errors (includes retries) */ + mta_bot, /* TRUE if BOT seen */ + mta_eot, /* TRUE if EOT seen */ + mta_eof, /* TRUE if EOF (tapemark) seen */ + mta_frms, /* # frames (bytes) read in record */ + mta_retry; /* # times to retry a failing op */ + + long d_tloc; /* Location in bytes read/written from BOT */ + int d_recs, /* # recs in tape so far */ + d_frecs, /* # recs in current file so far */ + d_files, /* # files (tapemarks) seen so far */ + d_herrs, /* Hard errors (unrecoverable) */ + d_serrs; /* Soft errors (includes retries) */ +}; +struct dev dvi = { "In" }; +struct dev dvo = { "Out" }; + +/* Values for d_istape. Those for d_vfmt come from vmtape.h */ +#define MTYP_NULL 0 /* Null device */ +#define MTYP_VIRT 1 /* Virtual magtape, d_vfmt has format */ +#define MTYP_HALF 2 /* Half-inch reel magtape */ +#define MTYP_QIC 3 /* Quarter-inch cartridge magtape */ +#define MTYP_8MM 4 /* 8mm cartridge magtape */ +#define MTYP_4MM 5 /* 4mm DDS/DAT cartridge magtape */ +char *mtypstr[] = { + "nulldev", + "virtual", + "1/2\"", + "QIC", + "8mm", + "4mm" +}; + + +int cmdsget(int ac, char **av); +int docopy(void); + +int devbuffer(struct dev *d, char *buffp, rsiz_t blen); +int devopen(struct dev *d, int wrtf); +int devclose(struct dev *d); +int devread(struct dev *d); +int devwrite(struct dev *d, unsigned char *buff, rsiz_t len); + +int devwerr(struct dev *d, int err); +int devweof(struct dev *d); +int devweot(struct dev *d); + +int os_mtopen(struct dev *dp, int wrtf); +int os_mtread(struct dev *dp); +int os_mtfsr(struct dev *dp); +void os_mtstatus(struct dev *dp, FILE *f); +int os_mtclose(struct dev *dp); +int os_mtwrite(struct dev *dp); +int os_mtweof(struct dev *dp); +void os_mtclrerr(int fd); + +void errhan(void *arg, struct vmtape *t, char *s); +void efatal(char *errmsg); +void swerror(char *fmt, ...); + +#if CENV_SYS_T20 +void t20status(struct dev *d, FILE *f, int swd, int cnt); +#endif + +#if 0 +static char *dupcstr(); /* Use the one from vmtape.c for now */ +#endif + +/* Error handling */ + +/* Copied from OSDSUP.C */ + +char * +os_strerror(int err) +{ + if (err == -1 && errno != err) + return os_strerror(errno); +#if CENV_SYSF_STRERROR + return strerror(err); +#else +# if CENV_SYS_UNIX + { +#if !CENV_SYS_XBSD + extern int sys_nerr; + extern char *sys_errlist[]; +#endif + if (0 < err && err <= sys_nerr) + return (char *)sys_errlist[err]; + } +# endif + if (err == 0) + return "No error"; + else { + static char ebuf[30]; + sprintf(ebuf, "Unknown-error-%d", err); + return ebuf; + } +#endif /* !CENV_SYSF_STRERROR */ +} + +void errhan(void *arg, struct vmtape *t, char *s) +{ + fprintf(logf, "; %s: %s\n", t->mt_devname, s); +} + + +void efatal(char *errmsg) /* print error message and exit */ + /* error message string */ +{ + fflush(stdout); + fprintf(logf, "\n?%s\n",errmsg); + exit(1); +} + + +static int do_tdtest(void); + +int +main(int argc, char **argv) +{ + register struct dev *d; + int ret; + + logf = stderr; + signal(SIGINT, exit); /* Allow int to terminate log files etc */ + + vmt_init(&dvi.d_vmt, "TapeIn"); + dvi.d_vmt.mt_errhan = errhan; + dvi.d_vmt.mt_errarg = &dvi.d_vmt; + vmt_init(&dvo.d_vmt, "TapeOut"); + dvo.d_vmt.mt_errhan = errhan; + dvo.d_vmt.mt_errarg = &dvo.d_vmt; + + if (ret = cmdsget(argc, argv)) /* Parse and handle command line */ + exit(ret); + + + if (!sw_logpath) + logf = stderr; + else { + if ((logf = fopen(sw_logpath, "w")) == NULL) { + logf = stderr; + fprintf(logf, "; Cannot open log file \"%s\", using stderr.\n", + sw_logpath); + } + } + + /* Special test? */ + if (sw_tdtest) { + exit(do_tdtest()); + } + + /* Set up defaults for devices, and log all params if requested */ + + /* bs=, if specified, becomes default for both in and out */ + if (!dvi.d_recsiz) dvi.d_recsiz = sw_bothsiz; + if (!dvo.d_recsiz) dvo.d_recsiz = sw_bothsiz; + if (sw_verbose) { +#define LOGTAPE(d,name) \ + fprintf(logf, "; %s tape spec \"%s\" (Type: %s", \ + name, (d).d_path, mtypstr[(d).d_istape]); \ + if ((d).d_istape == MTYP_VIRT) \ + fprintf(logf, " format: %s", vmt_fmtname((d).d_vfmt)); \ + else \ + fprintf(logf, ")\n") + LOGTAPE(dvi, " Input"); + LOGTAPE(dvo, "Output"); +#undef LOGTAPE + + if (dvi.d_recsiz) + fprintf(logf, "; Input record size %ld\n", (long)dvi.d_recsiz); + if (dvo.d_recsiz) + fprintf(logf, "; Output record size %ld\n", (long)dvo.d_recsiz); + if (sw_maxfile) + fprintf(logf, "; Max tapemarks (files) to process: %d\n", sw_maxfile); + if (sw_maxrec) + fprintf(logf, "; Max records to process: %d\n", sw_maxrec); + if (sw_logpath) + fprintf(logf, "; Using logging path %s\n", sw_logpath); + } + + /* Open I/O files as appropriate */ + if (!devopen(&dvi, FALSE)) /* Open for reading */ + exit(1); + if (!devopen(&dvo, TRUE)) /* Open for writing */ + exit(1); + + /* Set up buffering. This is somewhat tricky due to all the + ** possible situations; future parameters may complicate it by specifying + ** record truncation, padding, or conversion of some kind. + ** For now, the primary intent is to ensure we have a buffer large + ** enough to handle the maximum expected record size, while still + ** retaining the capability of asking for less than the maximum + ** possible (64K) in case memory usage is a concern (less and less likely + ** these days). + ** + ** Since there are no funny parameters, currently just one buffer + ** is used, whichever of input or output is larger. + */ + if (dvi.d_blen < dvo.d_blen) + dvi.d_blen = dvo.d_blen; + else if (dvi.d_blen > dvo.d_blen) + dvo.d_blen = dvi.d_blen; + + if (!devbuffer(&dvi, (char *)NULL, dvi.d_blen) + || !devbuffer(&dvo, (char *)dvi.d_buff, dvi.d_blen)) { + exit(1); + } + + + /* Do it! */ + fprintf(logf, "; Copying from \"%s\" to \"%s\"...\n", dvi.d_path, + dvo.d_path ? dvo.d_path : NULLDEV); + if (!docopy()) { + fprintf(logf, "; Stopped unexpectedly.\n"); + ret = FALSE; + } + if (!devclose(&dvo)) { + fprintf(logf, "; Error closing output.\n"); + ret = FALSE; + } + for (d = &dvi; d; d = (d == &dvi) ? &dvo : NULL) { + if (d->d_istape) + fprintf(logf, "; %3s: %d+%d errs, %d files, %d recs, %ld bytes\n", + d->d_pname, d->mta_herr, d->mta_serr, + d->d_files, d->d_recs, d->d_tloc); + } + + fclose(logf); + exit(ret ? 0 : 1); +} + + +int docopy(void) +{ + int err, ret = TRUE; + int done = FALSE; + + /* If skipping input, do that first */ + if (sw_fileskip) { + /* Skip files */ + while ((err = devread(&dvi)) >= 0) { + if (vmt_isateof(&dvi.d_vmt)) { /* Hit tapemark? */ + dvi.d_files++; /* Bump count of files */ + dvi.d_frecs = 0; + if (dvi.d_files >= sw_fileskip) + break; + } + if (vmt_isateot(&dvi.d_vmt)) + break; + } + } + if (sw_recskip && (err >= 0) && !vmt_isateot(&dvi.d_vmt)) { + /* Skip records (after skipping files) */ + while ((err = devread(&dvi)) >= 0) { + if (vmt_framecnt(&dvi.d_vmt) && (dvi.d_frecs >= sw_recskip)) + break; + if (vmt_isateof(&dvi.d_vmt)) { /* Hit tapemark? */ + dvi.d_files++; /* Bump count of files */ + dvi.d_frecs = 0; + break; + } + if (vmt_isateot(&dvi.d_vmt)) + break; + } + } + if (err >= 0 && !vmt_isateot(&dvi.d_vmt)) while (!done) { + /* Get a record/tapemark */ + if ((err = devread(&dvi)) < 0) + break; + + /* Now copy results to output device */ + if (vmt_framecnt(&dvi.d_vmt)) { + if (!devwrite(&dvo, dvi.d_buff, + (rsiz_t)vmt_framecnt(&dvi.d_vmt))) { + fprintf(logf, "; Stopped due to output write error: %s\n", + os_strerror(-1)); + ret = FALSE; + break; + } + if (sw_maxrec && dvo.d_recs >= sw_maxrec) + done = TRUE; /* Stop after all checks done */ + } + +#if 0 +/* XXX This needs more thought/work so it isn't always called each time + an EOF is encountered by devread() of virtual tape. +*/ + if (err == FALSE) + devwerr(&dvo, err); +#endif + + if (vmt_isateof(&dvi.d_vmt)) { /* Hit tapemark? */ + long ofrecs = dvi.d_frecs; /* Remember # recs in current file */ + int wwon; + + dvi.d_files++; /* Bump count of files */ + dvi.d_frecs = 0; + wwon = devweof(&dvo); + + if (ofrecs == 0 && !sw_peot) { /* Double tapemark? */ + --dvi.d_files; /* Don't count as another file */ + if (wwon) --dvo.d_files; + break; /* Double tapemark == stop */ + } + if (sw_maxfile && dvo.d_files >= sw_maxfile) + break; + } + + if (vmt_isateot(&dvi.d_vmt)) /* If input tape has reached EOT, */ + break; /* quit loop, we're done! */ + } + + if (err < 0) { + fprintf(logf, "; Stopped due to input read error: %s\n", + os_strerror(-1)); + ret = FALSE; + } + + /* Copy either done or aborted. Finalize output. This call + ** ensures that all output buffers are flushed and the tape dir, if + ** one, is written out. + */ + devweot(&dvo); /* Write "EOT" */ + + return ret; +} + + +/* Special test hack. +** Read in the input tape control file, if one, and output +** parsed result to stdout. +** This is used to verify that parsing works. +*/ +static int +do_tdtest(void) +{ + FILE *tdif, *tdof; + char *errstr = NULL; + + if (!dvi.d_cpath) { + fprintf(logf, "; No input tape directory specified\n"); + return 1; + } + if (!dvo.d_cpath) { + fprintf(logf, "; No output tape directory specified\n"); + return 1; + } + + if (dvi.d_path && dvi.d_istape == MTYP_VIRT) { + struct vmtattrs v; + v.vmta_mask = VMTA_PATH | VMTA_FMTREQ; + strncpy(v.vmta_path, dvi.d_path, sizeof(v.vmta_path)); + v.vmta_fmtreq = dvi.d_vfmt; + if (!vmt_attrmount(&dvi.d_vmt, &v)) /* Try mounting RO */ + errstr = "vmt error"; + } else { /* Sigh, painful method */ + if (dvi.d_cpath[0] == '\0') + tdif = stdin; + else if ((tdif = fopen(dvi.d_cpath, "r")) == NULL) { + errstr = os_strerror(-1); + } + if (!errstr && !tdr_scan(&dvi.d_vmt, tdif, dvi.d_cpath)) + errstr = "parse error"; + } + + if (errstr) { + fprintf(logf,"; Problem with tape desc file \"%s\": %s\n", + dvi.d_cpath, errstr); + return 1; + } + + /* If here, parse of tape desc file won, so output it. */ + if (strcmp(dvo.d_cpath, "") == 0) { + tdof = stdout; + } else if ((tdof = fopen(dvo.d_cpath, "w")) == NULL) { + fprintf(logf,"Cannot open tape desc file \"%s\": %s\n", + dvo.d_cpath, os_strerror(-1)); + return FALSE; + } + + td_fout(&dvi.d_vmt.mt_tdr, tdof, ""); /* Just show results */ + return 0; +} + +int swerrs = 0; + +void swerror(char *fmt, ...) +{ + ++swerrs; + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + putc('\n', stderr); +} + + +int +cmdsget(int ac, char **av) +{ + register char *cp, *arg; + register int plen; + struct dev *d; + long ltmp; + + dvi.d_istape = dvo.d_istape = MTYP_NULL; + + while (--ac > 0 && (cp = *++av)) { + if (arg = strchr(cp, '=')) /* If arg furnished for param, */ + *arg++ = '\0'; /* split arg off */ + if ((plen = strlen(cp)) <= 0) + break; /* Bad param */ + + /* Now identify parameter. No case folding for now, sigh */ + if (*cp == 'i' || *cp == 'o') { + /* Handle {i,o}{t,d,f,b} */ + if (!arg) { + swerror("Parameter requires arg: \"%s\"", *av); + continue; + } + d = (*cp == 'i') ? &dvi : &dvo; + switch (*++cp) { + case 't': + if (strlen(cp) > 3) break; + if (d->d_path) { + swerror("Param already specified: \"%s\"", *av); + continue; + } + d->d_path = arg; + switch (*++cp) { + case '\0': + case 'h': d->d_istape = MTYP_HALF; continue; + case 'q': d->d_istape = MTYP_QIC; continue; + case '8': d->d_istape = MTYP_8MM; continue; + case '4': d->d_istape = MTYP_4MM; continue; + case 'v': d->d_istape = MTYP_VIRT; + switch (*++cp) { + case '\0': + case 'u': d->d_vfmt = VMT_FMT_UNK; continue; + case 'r': d->d_vfmt = VMT_FMT_RAW; continue; + case 'c': d->d_vfmt = VMT_FMT_TPC; continue; + case 'e': d->d_vfmt = VMT_FMT_TPE; continue; + case 's': d->d_vfmt = VMT_FMT_TPS; continue; +#if VMTAPE_ITSDUMP + case 'i': d->d_vfmt = VMT_FMT_ITS; continue; +#endif +#if VMTAPE_T20DUMP + case '6': d->d_vfmt = VMT_FMT_DUMPER6; continue; +#endif + default: + swerror("Unknown format spec: \"%s\"", cp); + break; /* Drop thru to fail */ + } + default: + swerror("Unknown device spec: \"%s\"", cp); + break; /* Drop thru to fail */ + } + break; + case 'c': + case 'd': + if (*++cp) break; /* Ensure param name done */ + if (d->d_cpath) { + swerror("Param already specified: \"%s\"", *av); + continue; + } + d->d_cpath = arg; + continue; + case 'f': + if (*++cp) break; /* Ensure param name done */ + if (d->d_rpath) { + swerror("Param already specified: \"%s\"", *av); + continue; + } + d->d_rpath = arg; + continue; + case 'b': + if (*++cp != 's' || *++cp) /* Ensure param name done */ + break; + if (d->d_recsiz) { + swerror("Param already specified: \"%s\"", *av); + continue; + } + if (sscanf(arg, "%ld", <mp) != 1) + swerror("Bad arg to %s: \"%s\"", *av, arg); + d->d_recsiz = ltmp; + continue; + + /* Default just drops thru to fail */ + } + swerror("Unknown parameter \"%s\"", *av); + continue; + } + switch (plen) { + case 1: + if (*cp == 'v') { + sw_verbose = TRUE; + continue; + } + break; + case 2: + if (strcmp(cp, "bs")==0) { + if (!arg || sscanf(arg, "%ld", <mp) != 1) + swerror("Bad arg to bs: \"%s\"", arg ? arg : ""); + sw_bothsiz = ltmp; + continue; + } + break; + case 3: + if (strcmp(cp, "log")==0) { + if (!(sw_logpath = arg)) + swerror("Bad arg to log: \"\""); + continue; + } + break; + case 4: + if (strcmp(cp, "rcnt")==0) { + if (!arg || sscanf(arg, "%d", &sw_maxrec) != 1) + swerror("Bad arg to rcnt: \"%s\"", arg ? arg : ""); + continue; + } + if (strcmp(cp, "fcnt")==0) { + if (!arg || sscanf(arg, "%d", &sw_maxfile) != 1) { + swerror("Bad arg to fcnt: \"%s\"", arg ? arg : ""); + } + continue; + } + if (strcmp(cp, "peot")==0) { + sw_peot = TRUE; + continue; + } + if (strcmp(cp, "test")==0) { + sw_tdtest = TRUE; + continue; + } + break; + case 5: + if (strcmp(cp, "rskip")==0) { + if (!arg || sscanf(arg, "%d", &sw_recskip) != 1) + swerror("Bad arg to rskip: \"%s\"", arg ? arg : ""); + continue; + } + if (strcmp(cp, "fskip")==0) { + if (!arg || sscanf(arg, "%d", &sw_fileskip) != 1) { + swerror("Bad arg to fskip: \"%s\"", arg ? arg : ""); + } + continue; + } + break; + case 7: + if (strcmp(cp, "verbose")==0) { + sw_verbose = TRUE; + continue; + } + break; + } + swerror("Unknown parameter \"%s\"\n%s", *av, nusage); + return 1; + } + + /* Ensure input/output specs make sense. Must have either T or D */ + if (!dvi.d_path) { + /* Normally an error, but allow it if id= and od= both + ** exist, without an ot= spec -- for testing tapedir scanning. + */ + if (dvi.d_cpath && !dvi.d_rpath + && !dvo.d_path && dvo.d_cpath && !dvo.d_rpath) + sw_tdtest = TRUE; + else + swerror("Must have it= parameter"); + } + if (!dvo.d_path && dvo.d_rpath) { + /* Disallow of= without ot= for now, because type of output + ** device is ambiguous. + */ + swerror("Must have ot= to use of="); + } + + /* Check for any parameter errors */ + if (swerrs) { + fprintf(stderr, "%s", nusage); + return swerrs; + } + + return 0; +} + +/* Generic device routines (null, virtual, and real) +** Open, Close, Read, Write, Write-EOF, Write-EOT. +*/ + +int devopen(register struct dev *d, int wrtf) +{ + char *cpath, *rpath; + + /* Generate default filenames in consistent fashion and open them. + ** See table in comments near switch descriptions. + */ + if (d->d_istape == MTYP_VIRT) { +#if 1 /* New regime */ + struct vmtattrs v; + + v.vmta_mask = VMTA_FMTREQ | VMTA_MODE | VMTA_PATH | VMTA_UNZIP; + v.vmta_fmtreq = d->d_vfmt; + v.vmta_mode = wrtf ? VMT_MODE_CREATE : VMT_MODE_RDONLY; + v.vmta_unzip = TRUE; + strncpy(v.vmta_path, d->d_path, sizeof(v.vmta_path)); + if (d->d_cpath) { + v.vmta_mask |= VMTA_CTLPATH; + strncpy(v.vmta_ctlpath, d->d_cpath, sizeof(v.vmta_ctlpath)); + } + if (d->d_rpath) { + v.vmta_mask |= VMTA_DATPATH; + strncpy(v.vmta_datpath, d->d_rpath, sizeof(v.vmta_datpath)); + } + if (!vmt_attrmount(&d->d_vmt, &v)) { /* Try mounting it */ + fprintf(logf, "; Couldn't mount %sput virtual tape: %s\n", + d->d_pname, d->d_path); + return FALSE; + } + +#else /* Old regime */ + /* Use path as cpath; generate rawdata filename */ + if (!(cpath = d->d_cpath)) { + /* Default cpath to path */ + cpath = d->d_path; + } + if (!(rpath = d->d_rpath)) { + /* Default rpath to path.tap, unless path ends with .tdr + ** in which case replace the .tdr with .tap. + */ + char *s; + rpath = malloc(strlen(d->d_path)+4+1); + if (!rpath) { + fprintf(logf, "; Cannot malloc data filename\n"); + return FALSE; + } + strcpy(rpath, d->d_path); + if (!((s = strrchr(rpath, '.')) && (strcmp(s, ".tdr")==0))) + s = rpath + strlen(rpath); + strcpy(s, ".tap"); + } + + /* Defaults all set up, store them back in structure */ + d->d_cpath = cpath; + d->d_rpath = rpath; + + /* Open up virtual files. Need special hackery if either + ** the tapedesc or rawdata path was explicitly given. + */ + if (!vmt_xmount(&(d->d_vmt), d->d_cpath, d->d_rpath, wrtf)) { + fprintf(logf, "; Couldn't mount %sput virtual tape: %s\n", + d->d_pname, d->d_path); + return FALSE; + } +#endif + + + } else { + /* Default filenames for real device */ + cpath = d->d_cpath; /* Desc file may not be wanted */ + if (!(rpath = d->d_rpath)) + rpath = d->d_path; /* Data file defaults to tapefile */ + + /* Defaults all set up, store them back in structure */ + d->d_cpath = cpath; + d->d_rpath = rpath; + + if (d->d_rpath) { + /* Open real tape device */ + if (strcmp(d->d_rpath, "") == 0) { + d->d_fd = wrtf ? FD_STDOUT : FD_STDIN; + } else if (!os_mtopen(d, wrtf)) { + fprintf(logf, "; Cannot open %sput: %s\n", + d->d_pname, os_strerror(-1)); + return FALSE; + } + } + + /* Set up VMT vars, with optional tape-info if specified. + ** Note virtual tape isn't set here; vmt_mount already did that. + */ + switch (d->d_istape) { + case MTYP_NULL: d->d_vmt.mt_devname = "NUL"; break; + case MTYP_HALF: d->d_vmt.mt_devname = "MTA"; break; + case MTYP_QIC: d->d_vmt.mt_devname = "QIC"; break; + case MTYP_8MM: d->d_vmt.mt_devname = "8MM"; break; + case MTYP_4MM: d->d_vmt.mt_devname = "4MM"; break; + } + + + /* See if optional tapedesc for non-virtual drive. If so, + ** fake out virtual tape code to think it has a tapedesc. + ** This is ugly and fragile. + */ + if (d->d_cpath) { + + if (!(d->d_vmt.mt_filename = dupcstr(d->d_cpath))) { + fprintf(logf, "; malloc failed for cpath dupcstr\n"); + return FALSE; + } + if (strcmp(d->d_cpath, "") == 0) { + d->d_vmt.mt_ctlf = (wrtf ? stdout : stdin); + } else if ((d->d_vmt.mt_ctlf = + fopen(d->d_cpath, (wrtf ? "w" : "r"))) == NULL) { + fprintf(logf,"Cannot open tape desc file \"%s\": %s\n", + d->d_cpath, os_strerror(-1)); + return FALSE; + } + + /* Tape-desc file open, now parse it if reading */ + if (!wrtf && !tdr_scan(&(d->d_vmt), d->d_vmt.mt_ctlf, d->d_cpath)) { + fprintf(logf, "; Cannot parse tape desc\n"); + return FALSE; + } + /* Do final setup if writing */ + if (wrtf && d->d_rpath) { + if (!(d->d_vmt.mt_datpath = dupcstr(d->d_rpath))) { + fprintf(logf, "; malloc failed for rpath dupcstr\n"); + return FALSE; + } + } + } + + } + + + /* Set default buffer length */ + if (d->d_blen = d->d_vmt.mt_tdr.tdmaxrsiz) { + if (d->d_recsiz) { /* Explicit record size spec? */ + if (d->d_blen <= d->d_recsiz) /* If it's bigger, */ + d->d_blen = d->d_recsiz; /* adjust quietly */ + else /* else warn user */ + fprintf(logf, "; Tapedir forcing larger %sput record size (%ld instead of %ld)\n", + d->d_pname, d->d_blen, d->d_recsiz); + + } + } else /* No tapedir, use spec if any */ + d->d_blen = d->d_recsiz ? d->d_recsiz : MAXRECSIZE; + + return TRUE; +} + +/* Set up device buffering. +** Must be called after device already opened. +** Probably will lose if called after I/O done. +** If buffp set, will use that with blen and not free it when closed. +** If buffp NULL, will allocate buffer of size blen, unless blen is 0 +** in which case it figures out a default. +*/ +int devbuffer(struct dev *d, char *buffp, rsiz_t blen) +{ + if (blen <= 0) { + blen = MAXRECSIZE; /* No size spec, use a safe default */ + buffp = NULL; /* Ensure any pointer arg is ignored */ + } + + d->d_bisdyn = FALSE; + if (buffp == NULL) { + /* Always round actual buffer size upwards to be modulo-512 in case + ** either input or output has to deal with QIC drives, otherwise the + ** unix I/O call will fail without even trying to do anything! + */ + blen = (blen + 511) & ~511; /* Relies on fact 512 is power of 2 */ + if ((buffp = malloc(blen)) == NULL) { + fprintf(logf, "; Cannot malloc %sput buffer (size %ld)\n", + d->d_pname, blen); + return FALSE; + } + d->d_bisdyn = TRUE; + } + d->d_blen = blen; + d->d_buff = (char8 *)buffp; + d->d_iop = d->d_buff; + d->d_buse = 0; + + return TRUE; +} + +int devclose(struct dev *d) +{ + int ret; + + if (d->d_istape == MTYP_VIRT) { + ret = vmt_unmount(&d->d_vmt); /* Close virtual tape */ + } else { + ret = TRUE; + if (d->d_rpath) + ret = os_mtclose(d); /* Close real tape */ + if (d->d_cpath) { + struct vmtape *t = &d->d_vmt; /* Close tapedesc */ + + rewind(t->mt_ctlf); /* Rewind in case not the first update */ + td_fout(&t->mt_tdr, t->mt_ctlf, t->mt_datpath); + fflush(t->mt_ctlf); + if (ferror(t->mt_ctlf)) /* Check to see if any errors */ + ret = FALSE; + } + } + + if (d->d_bisdyn && d->d_buff) { + free((char *)(d->d_buff)); + d->d_buff = NULL; + } + return ret; +} + + +/* Read one data unit (record, tapemark, etc) from device +** Returns 1 if read something +** Returns 0 if read nothing, but no error +** Returns -1 if error of some kind +*/ + +int devread(struct dev *d) +{ + int ret; + + if (d->d_istape == MTYP_VIRT) { + /* Virtual tape, read in and change vmt params directly */ + ret = vmt_rget(&(d->d_vmt), d->d_buff, (size_t)d->d_blen); + } else if (d->d_rpath) { + /* Real tape, attempt physical read */ + + /* Still to do? Handle dir-only case, plus dir+dev case? */ + ret = os_mtread(d); /* Attempt read from device */ + if (ret < 0) { + fprintf(logf, "; %sput device read error: %s\n", + d->d_pname, os_strerror(-1)); + return ret; + } + + if (d->d_vmt.mt_frames = d->mta_frms) { /* Read any data? */ + d->d_buse = d->mta_frms; /* Say this much of buffer used */ + d->d_iop = d->d_buff; + } + d->d_vmt.mt_eof = d->mta_eof; + d->d_vmt.mt_eot = d->mta_eot; + d->d_vmt.mt_bot = d->mta_bot; + + } else { + /* Null device, read nothing */ + d->d_vmt.mt_frames = 0; /* No data read */ + d->d_vmt.mt_eof = TRUE; /* At BOF, EOF, and EOT! */ + d->d_vmt.mt_eot = TRUE; + d->d_vmt.mt_bot = TRUE; + ret = TRUE; + } + + + if (d->d_vmt.mt_frames) { + d->d_tloc += d->d_vmt.mt_frames; + d->d_recs++; /* Got a record */ + d->d_frecs++; /* Bump rec cnt for current file */ + } + return ret; +} + +/* Write one record to device. +** For now, note mtawrite assumes d_buff and d_buse are args. +*/ +int devwrite(struct dev *d, unsigned char *buff, rsiz_t len) +{ + int ret = TRUE; + + if (len) { + if (d->d_istape == MTYP_VIRT) { + if (ret = vmt_rput(&(d->d_vmt), buff, (size_t)len)) { + d->d_tloc += len; + d->d_recs++; + d->d_frecs++; + } + } else { + if (d->d_cpath) /* Add to tapedir if one */ + td_recapp(&(d->d_vmt.mt_tdr), len, 1, 0); + if (d->d_rpath) { /* Output to real device if one */ + d->d_recsiz = d->d_buse = len; + ret = os_mtwrite(d); + if (d->mta_frms) { + d->d_tloc += d->mta_frms; + d->d_recs++; + d->d_frecs++; + } + } + } + } + + return ret; +} + +int devwerr(struct dev *d, int err) +{ + if (d->d_istape == MTYP_VIRT) + return vmt_eput(&(d->d_vmt), err, TRUE); + + if (d->d_cpath) { /* Have tapedir? */ + if (!td_recapp(&d->d_vmt.mt_tdr, (long)0, 1, err)) { + fprintf(logf, "devwerr: td_recapp malloc failed"); + return FALSE; + } + + } + return TRUE; +} + +int devweof(struct dev *d) +{ + int ret = TRUE; + + if (d->d_istape == MTYP_VIRT) + ret = vmt_eof(&(d->d_vmt)); + else { + if (d->d_cpath) { /* Have tapedir? */ + /* Extracted from vmt_eof(), see comments there */ + vmt_iobeg(&d->d_vmt, TRUE); /* Set up for write */ + if (!td_recapp(&d->d_vmt.mt_tdr, (long)0, 0, 0)) { + fprintf(logf, "devweof: td_recapp malloc failed"); + ret = FALSE; + } + } + if (d->d_rpath) + ret = os_mtweof(d) ? ret : FALSE; /* Keep F if already F */ + } + if (ret) { + d->d_files++; /* Bump count of files written */ + d->d_frecs = 0; + } + return ret; +} + +int devweot(struct dev *d) +{ + if (d->d_istape == MTYP_VIRT) + return vmt_eot(&(d->d_vmt)); + + /* Otherwise devclose() will do all that's needed */ + + return TRUE; +} + +/* Magtape handling routines +*/ + +#if CENV_SYS_T20 + +/* MAGTAPE DEVICE STATUS BITS */ + +#define MT_ILW monsym("MT%ILW") /* ILLEGAL WRITE */ +#define MT_DVE monsym("MT%DVE") /* DEVICE ERROR */ +#define MT_DAE monsym("MT%DAE") /* DATA ERROR */ +#define MT_SER monsym("MT%SER") /* SUPPRESS ERROR RECOVERY PROCEDURES */ +#define MT_EOF monsym("MT%EOF") /* EOF (FILE MARK) */ +#define MT_IRL monsym("MT%IRL") /* INCORRECT RECORD LENGTH */ +#define MT_BOT monsym("MT%BOT") /* BEGINNING OF TAPE */ +#define MT_EOT monsym("MT%EOT") /* END OF TAPE */ +#define MT_EVP monsym("MT%EVP") /* EVEN PARITY */ +#define MT_DEN monsym("MT%DEN") /* DENSITY (0 IS 'NORMAL') */ +#define MT_CCT monsym("MT%CCT") /* CHARACTER COUNTER */ +#define MT_NSH monsym("MT%NSH") /* DATA MODE OR DENS NOT SUPPORTED BY HDW */ + +static int acs[5]; /* For any JSYS that needs it */ + +#endif /* CENV_SYS_T20 */ + +int os_mtopen(struct dev *dp, int wrtf) +{ + int fd; + +#if CENV_SYS_T20 + acs[1] = monsym("GJ%NEW")|monsym("GJ%SHT"); + acs[2] = (int)(dp->d_path-1); + if (jsys(GTJFN, acs) <= 0) + return FALSE; + fd = (acs[1] &= RH); + acs[2] = FLD(monsym(".GSDMP"),monsym("OF%MOD")) + | monsym("OF%RD"); + if (jsys(OPENF, acs) <= 0) { + acs[1] = fd; + jsys(RLJFN, acs); + return FALSE; + } +#else + fd = open(dp->d_path, (wrtf ? O_RDWR : O_RDONLY)|O_BSIZE_8, 0600); + if (fd < 0) + return FALSE; +#endif + dp->mta_retry = 5; /* Default for now */ + dp->d_fd = fd; + return TRUE; +} + +int os_mtclose(struct dev *dp) +{ +#if CENV_SYS_T20 +#else + return close(dp->d_fd); +#endif +} + +int os_mtread(struct dev *dp) +{ + int retry = dp->mta_retry; +#if CENV_SYS_T20 + + static int iov[2] = {0, 0}; + int flgs; + + dp->mta_frms = dp->mta_eof = 0; + dp->mta_bot = dp->mta_eot = 0; + for (; retry >= 0; --retry) { + iov[0] = XWD(-(dp->d_blen/sizeof(int)),((int)(int *)(dp->d_buff))-1); + acs[1] = dp->d_fd; + acs[2] = (int)&iov; + if (jsys(DUMPI, acs) > 0) { + fprintf(logf, "; Warning: DUMPI won for max rec size %ld!\n", + (long)dp->d_blen); + dp->mta_frms = dp->d_blen; + return 1; + } + /* Error return is normal case, since record size varies */ + switch (acs[1]) { + case monsym("IOX4"): /* EOF (tape mark) */ + case monsym("IOX5"): /* Device or data error (rec length) */ + acs[1] = dp->d_fd; + if (jsys(GDSTS, acs) <= 0) { + fprintf(logf, "; Tape GDSTS%% error: %s\n", os_strerror(-1)); + os_mtclrerr(dp->d_fd); + dp->mta_herr++; + return 0; + } + break; + default: + fprintf(logf, "; Tape DUMPI%% error: %s\n", os_strerror(-1)); + os_mtclrerr(dp->d_fd); + dp->mta_herr++; + return 0; + } + + /* Analyze "error". acs[2] has flags, [3] has count in LH */ + flgs = acs[2]; + t20status(dp, logf, flgs, acs[3]); + dp->mta_frms = ((unsigned)acs[3]) >> 18; /* Find cnt of data */ + +/* Opening in industry-compatible mode apparently works to force use of +** frame count, not word count */ +/* dp->mta_frms *= sizeof(int); */ + os_mtclrerr(dp->d_fd); + if (flgs & (MT_DVE|MT_DAE)) { + fprintf(logf, "; Tape error: %s\n", + (flgs & MT_DVE) ? "Device" : "Data"); + dp->mta_serr++; + continue; /* Try again */ + } + if (flgs & MT_EOF) + dp->mta_eof = TRUE; + if (flgs & MT_EOT) + dp->mta_eot = TRUE; + if (flgs & MT_BOT) + dp->mta_bot = TRUE; + return 1; + } + dp->mta_herr++; + +#else /* !CENV_SYS_T20 */ + + dp->mta_frms = dp->mta_eof = 0; + dp->mta_bot = dp->mta_eot = 0; + for (; retry >= 0; --retry) { + switch (dp->mta_frms = read(dp->d_fd, dp->d_buff, dp->d_blen)) { + case 0: /* Tapemark */ + dp->mta_eof = TRUE; + return TRUE; + + default: /* Assume record read */ + if (dp->mta_frms <= 0) { + case -1: /* Error */ + dp->mta_serr++; + fprintf(logf, "; Tape read error: %s\n", os_strerror(-1)); + os_mtstatus(dp, logf); /* Show full status */ + if (retry <= 0) { + if (os_mtfsr(dp)) { /* Try spacing over 1 rec */ + retry = dp->mta_retry; + dp->mta_herr++; + } else { + fprintf(logf, "; Cannot proceed past read error, aborting...\n"); + return -1; + } + } + continue; /* Try again */ + } + + /* Special hack for QIC -- pretend we read only the record size + ** specified, although actual read was greater in order to + ** keep I/O using modulo-512 counts. + */ + if (dp->d_istape == MTYP_QIC) { + if (dp->mta_frms > dp->d_recsiz) { + /* Adjust so devread() gets true tapeloc */ + dp->d_tloc += dp->mta_frms - dp->d_recsiz; + dp->mta_frms = dp->d_recsiz; + } + } else + if (dp->mta_frms >= dp->d_blen) + fprintf(logf, "; Warning: read max rec size %d!\n", + dp->mta_frms); + + + return TRUE; + } + } + dp->mta_frms = 0; +#endif + + return 0; /* For now */ +} + +int os_mtwrite(struct dev *dp) +{ + int retry = dp->mta_retry; +#if CENV_SYS_T20 + static int iov[2] = {0, 0}; + int flgs; + + dp->mta_frms = dp->mta_eof = 0; + dp->mta_bot = dp->mta_eot = 0; + for (; retry >= 0; --retry) { + iov[0] = XWD(-(dp->d_buse/sizeof(int)),((int)(int *)(dp->d_buff))-1); + acs[1] = dp->d_fd; + acs[2] = (int)&iov; + if (jsys(DUMPO, acs) > 0) { + dp->mta_frms = dp->d_buse; + return 1; + } + /* Error return */ + dp->mta_serr++; + fprintf(logf, "; Tape DUMPO%% error: %s\n", os_strerror(-1)); + os_mtclrerr(dp->d_fd); + dp->mta_herr++; + return 0; + } +#elif CENV_SYS_UNIX /* !CENV_SYS_T20 */ + char8 *iop = dp->d_buff; + int ret; + rsiz_t full = dp->d_buse; + rsiz_t used = full; + + dp->mta_frms = 0; + + /* Special hack for QIC -- must bump up length to modulo-512. + */ + if (dp->d_istape == MTYP_QIC) { + full = (used + 0777) & ~0777; /* Round up to 512-byte boundary */ + if (used < dp->d_recsiz) + fprintf(logf, "; Warning: partial record padded out (%ld => %ld)\n", + (long)used, (long)full); + if (full > dp->d_blen) { + fprintf(logf, "; Internal bug: padout exceeds buffer (%ld > %ld)\n", + (long)full, (long)dp->d_blen); + full = dp->d_blen; + } + if (full > used) { + memset(dp->d_buff + used, 0, full - used); /* Zap padding */ + dp->d_tloc += full - used; /* Add extra so devwrite */ + } /* knows true tapeloc */ + used = full; + } + + + do { + if ((ret = write(dp->d_fd, iop, used)) == used) { + dp->mta_frms += used; /* Won! */ + return 1; + } + /* Error of some kind */ + dp->mta_serr++; + fprintf(logf, "; (Rec %d, try %d) ", + dp->d_recs, dp->mta_retry - retry); + if (ret < 0) { + fprintf(logf, "Tape write error: %s\n", os_strerror(-1)); + } else { + fprintf(logf, "Tape write truncated: %d, shd be %ld\n", + ret, (long)used); + if (ret > 0) { + used -= ret; /* Update to write out rest */ + iop += ret; + dp->d_recs++; /* Sigh, stupid crock here */ + dp->d_frecs++; + dp->mta_frms += ret; + } + } + os_mtstatus(dp, logf); /* Show full status */ + } while (--retry > 0); /* Continue N times */ + dp->mta_herr++; +#endif + + return 0; /* For now */ +} + +int os_mtweof(struct dev *dp) /* Write a tapemark */ +{ +#if CENV_SYS_T20 + acs[1] = dp->d_fd; + acs[2] = monsym(".MOCLE"); + jsys(MTOPR, acs); +#else + struct mtop mtcmd; + mtcmd.mt_op = MTWEOF; + mtcmd.mt_count = 1; + if (ioctl(dp->d_fd, MTIOCTOP, &mtcmd) < 0) { + fprintf(logf, "; MTWEOF ioctl failed: %s\n", os_strerror(-1)); + dp->mta_herr++; + return FALSE; + } +#endif + return TRUE; +} + +int os_mtfsr(struct dev *dp) /* Forward Space Record (to inter-record gap)*/ +{ +#if CENV_SYS_T20 +/* acs[1] = dp->d_fd; + acs[2] = monsym(".MOCLE"); + jsys(MTOPR, acs); +*/ +#else + struct mtop mtcmd; + mtcmd.mt_op = MTFSR; + mtcmd.mt_count = 1; + if (ioctl(dp->d_fd, MTIOCTOP, &mtcmd) < 0) { + fprintf(logf, "; MTFSR ioctl failed: %s\n", os_strerror(-1)); + dp->mta_herr++; + return FALSE; + } +#endif + return TRUE; +} + +void os_mtclrerr(int fd) +{ +#if CENV_SYS_T20 + acs[1] = fd; + acs[2] = monsym(".MOCLE"); + jsys(MTOPR, acs); +#endif /* CENV_SYS_T20 */ +} + +#if CENV_SYS_T20 +void +t20status(register struct dev *d, register FILE *f, int swd, int cnt) +{ + fprintf(f, "; Tape status: Cnt %d", ((unsigned)cnt)>>18); + if (cnt & RH) fprintf(f, ",,%d", cnt & RH); + fprintf(f, " Flgs: %#o", swd&RH); + + if (swd & MT_ILW) fprintf(f, " ILW"); /* ILLEGAL WRITE */ + if (swd & MT_DVE) fprintf(f, " DVE"); /* DEVICE ERROR */ + if (swd & MT_DAE) fprintf(f, " DAE"); /* DATA ERROR */ + if (swd & MT_SER) fprintf(f, " SER"); /* SUPPRESS ERROR RECOVERY PROCS */ + if (swd & MT_EOF) fprintf(f, " EOF"); /* EOF (FILE MARK) */ + if (swd & MT_IRL) fprintf(f, " IRL"); /* INCORRECT RECORD LENGTH */ + if (swd & MT_BOT) fprintf(f, " BOT"); /* BEGINNING OF TAPE */ + if (swd & MT_EOT) fprintf(f, " EOT"); /* END OF TAPE */ + if (swd & MT_EVP) fprintf(f, " EVP"); /* EVEN PARITY */ + if (swd & MT_DEN) fprintf(f, " DEN=%d", /* DENSITY (0 IS 'NORMAL') */ + FLDGET(swd, MT_DEN)); + if (swd & MT_CCT) fprintf(f, " CCT=%d", /* CHARACTER COUNTER */ + FLDGET(swd, MT_CCT)); + if (swd & MT_NSH) fprintf(f, " NSH"); /* DATA MODE OR DENS NOT SUPPORTED */ + fprintf(f, "\n"); +} +#endif /* CENV_SYS_T20 */ + +void os_mtstatus(struct dev *dp, FILE *f) +{ +#if CENV_SYS_T20 +#elif CENV_SYS_UNIX + struct mtget mtstatb; + + if (ioctl(dp->d_fd, MTIOCGET, (char *)&mtstatb) < 0) { + fprintf(f, "; Couldn't get status for %s; %s\n", + dp->d_path, os_strerror(-1)); + return; + } + fprintf(f, "; Status for magtape %s:\n", dp->d_path); + fprintf(f, "; Type: %#x (vals in sys/mtio.h)\n", mtstatb.mt_type); +# if CENV_SYS_SUN + fprintf(f, "; Flags: %#o ->", mtstatb.mt_flags); + if (mtstatb.mt_flags & MTF_SCSI) fprintf(f, " SCSI"); + if (mtstatb.mt_flags & MTF_REEL) fprintf(f, " REEL"); + if (mtstatb.mt_flags & MTF_ASF) fprintf(f, " ABSFILPOS"); + fprintf(f, "\n"); + fprintf(f, "; Optim blkfact: %d\n", mtstatb.mt_bf); +# endif + + fprintf(f, "; Drive status: %#o (dev dep)\n", mtstatb.mt_dsreg); + fprintf(f, "; Error status: %#o (dev dep)\n", mtstatb.mt_erreg); + fprintf(f, "; Err - Cnt left: %ld\n", (long)mtstatb.mt_resid); +# if CENV_SYS_SUN + fprintf(f, "; Err - File num: %ld\n", (long)mtstatb.mt_fileno); + fprintf(f, "; Err - Rec num: %ld\n", (long)mtstatb.mt_blkno); +# endif + +#endif +} diff --git a/src/udlconv.c b/src/udlconv.c new file mode 100644 index 0000000..2ad987f --- /dev/null +++ b/src/udlconv.c @@ -0,0 +1,781 @@ +/* UDLCONV.C - Utility to convert DIR.LIST files into ITSDUMP tapedirs +*/ +/* $Id: udlconv.c,v 2.2 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: udlconv.c,v $ + * Revision 2.2 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* Originally called "dlmunch". + * Acts as a filter: + * Input is a DIR.LIST file from Alan Bawden's ITS archives. + * Output is a DIR.tdr file suitable for mounting as a virtual tape, + * provided ITSDUMP format is supported. + */ + +/* + Format of DIR.LIST list: + + (("system-name" "dir-name") + ("fn1" "fn2" "linkto" "author") + ... + ) + +"fn1" and "fn2" appear to have no quote chars. Every char within + the quotemarks is valid (including spaces!) + + is a decimal number of # bytes, format <'.'> + or NIL if entry is a link. + is either "7." or "36." I suppose others may be possible. + or NIL if entry is a link. +"linkto" is NIL for an ordinary file, else an ITS filespec of the name + pointed to. String format is: + "<';'><' '><' '>" + It's still unclear whether anything is quoted or not. + is the creation date. See below. + is the reference date. + Both dates are huge decimal numbers, i.e. <'.'> + which represent the CommonLisp/LispM convention of # seconds + since 1 Jan 1900 00:00:00 GMT. + This will fit within 32 bits, esp if unsigned. +"author" is the sname of the file's author, if known, and NIL otherwise. + + Suggest using sscanf's "%li" to read in the numeric formats. + + +Transformed into: + ITSFILE: dir fn1 fn2 {} + {} -> xdir xfn1 xfn2 + +where each word is all nonspace chars. Spaces are replaced by '~' within +a word. + +{} is optional. If present, it contains: + { } +where the first 4 are numeric and the last is a word. All are optional +but must be in this order; use 0 to default any of the first 4, and don't +specify the at all to default it. + +There is currently no way to transform an author name into an author index +without some master table matching names to indices. Someday DUMP may have +its format extended to include this info, so it's retained for now. + + +*/ +/* + From Alan: +The following structure describes the format of each entry: + + (defstruct (entry (:type list)) + pathname + fn1 + fn2 + length-in-bytes + byte-size + link-to + creation-date + reference-date + author + ) + +Except the first slot (`pathname') isn't stored in the DIR.LIST file, only +the part of the list starting with the `fn1'. + +The `link-to' is `NIL' for an ordinary file, and contains the name of the +file linked to (as a string) if the entry is a link. I no longer remember +if that string is properly quoted for an ITS filename parser. (I may never +have known, all this information was actually generated by calling Lisp +Machine directory hacking routines, so we're actually at the mercy of +Symbolics here.) + +Also, if the entry is a link, the `length-in-bytes' and `byte-size' are +both `NIL'. + +I think the `creation-date' and `reference-date' are in the standard Common +Lisp / Lisp Machine date format as a number of seconds since some known +instant (perhaps 1 Jan 1901 00:00:00 GMT?). + +The first element in the list stored in the DIR.LIST file is not the +description of a file. It is a two element list: ( ). + + + Date: Mon, 7 Dec 1992 23:35:37 GMT + From: Ken Harrenstien + + length-in-bytes + byte-size + + This information needs to be munched somehow to get it into (1) + length-in-words and (2) bytecnt+bytesize. The latter is some 6-bit + magic field, part of value 5 of a FILBLK. I don't at the moment know + what the magic values are, but they must be in ITS someplace. + +Well the byte-size is always either 7 or 36, so the transformation isn't +that hard. Also, even the ITS DUMP program fails to restore this +information properly. The description of the format of value 5 of a FILBLK +can be found as a comment near the end of SYSTEM;FSDEFS >. + + I think the `creation-date' and `reference-date' are in the standard + Common Lisp / Lisp Machine date format as a number of seconds since + some known instant (perhaps 1 Jan 1901 00:00:00 GMT?). + + Erg. I was hoping they were the decimal values of the 4th and 5th + FILBLK words. Now this is going to take a little more conversion than I + expected. If you could find out... + +I looked it up. These times are the number of seconds since +1 Jan 1900 00:00:00 GMT (I was a year wrong above). This is the same +format as described in RFC 738 (written by someone named "K. Harrenstien"). +The ITS DATIME library has routines that convert values in the format into +ITS dates... + +You can sanity check any conversion routines you write by checking that all +of the `reference-dates' translate back into times at midnight (eastern +standard or daylight as appropriate). + + Incidentally, the method doesn't have to involve DUMP format, although + that is simplest to reload. For example, to create most of the links, + I parsed the DIR.LIST link data into a bunch of :XFILE files, FTPed + them over, and executed them. Hack. + +That's pretty funny. You know, since the DIR.LIST files are written in +Lisp format, the -right- thing to do is to do this all from Maclisp! + +*/ + +#include +#include +#include + +/*#include "lread.h" */ +#ifndef LREAD_INCLUDED +#define LREAD_INCLUDED 1 +/* LREAD.H - originally a separate file, but now incorporated directly + * for simplicity. + */ + +#define NIL ((struct lnode *)0) +#define LTRUE (<ruenode) + +enum { + LT_NIL=0, /* Null list */ + LT_ATOM, /* Untyped atom (lvs points to string) */ + LT_LIST, /* List (lvl points to it) */ + LT_STR, /* Atom with "string" syntax (lvs points to string) */ + LT_NUM /* Atom converted to numerical value (lvi) */ +}; + +struct lnode { + struct lnode *lnxt; + int ltyp; + union { + long lvi; + struct { int ls_cnt; char *ls_ptr; } lvstr; + struct lnode *lvl; + } lval; +}; + +extern struct lnode ltruenode; /* Constant TRUE */ + +extern struct lnode *lread(FILE *); +extern struct lnode *lnget(void); +extern void lprint(FILE *, struct lnode *); + + +#endif /* ifndef LREAD_INCLUDED */ + + +struct filent { + /* Raw parsed strings */ + char *ffn1, *ffn2; + char *fslen, *fsbsz, *flink; + char *fscreat, *fsref, *fsauth; + + /* Derived data */ + char *fpathname; /* Local pathname */ + char *fldir, *flfn1, *flfn2; /* Link specs */ + long fcreat, fref; + long flen, fbsz; +}; + +struct lnode *dlist, *syslist, *lcur; +char *sysname = NULL; +char *mdrname = NULL; +#if 0 /* "dirname" is used by DECOSF string.h?!! */ +char *dirname = NULL; +#endif + +char usage[] = "\ +Usage: %s [switches] < DIR.LIST > DIR.tdr\n\ + -p - Prefix this path to all host filenames\n\ +"; +char *swprefix = NULL; +int swfiles = 1; +int swxlinks = 0; +int swlinks = 0; +int swlist = 0; + +int fesetup(struct filent *fe, struct lnode *l); +char *its2unixfn(char *cp, char *itsfn); +char *fn6quot(char *fn); +void outlink(struct filent *fe); +void outfile(struct filent *fe); + + +main(int argc, char *argv[]) +{ + register int i; + char *err = NULL; + + /* Handle switches */ + for (i = 0; ++i < argc; ) { + if (argv[i][0] == '-') switch (argv[i][1]) { + case 'p': + if (i+1 < argc) { + swprefix = argv[++i]; + continue; + } + err = "No arg for"; + break; + default: + err = "Unknown switch"; + break; + } else + err = "Unknown arg"; + + fprintf(stderr, "%s: %s \"%s\"\n", argv[0], err, argv[i]); + break; + } + if (err) { + fprintf(stderr, usage, argv[0]); + exit(1); + } + + /* Now gobble up input list */ + dlist = lread(stdin); + + if (dlist == NIL || dlist->ltyp != LT_LIST) { + printf("; No list read\n"); + exit(1); + } + if (dlist->lnxt) { + printf("; More than one thing read?\n"); + exit(1); + } + if (swlist) + lprint(stdout, dlist); + + dlist = dlist->lval.lvl; /* Get 1st thing on list */ + + syslist = (dlist->ltyp == LT_LIST) ? dlist->lval.lvl : NIL; + + if (syslist) { + sysname = (syslist->ltyp == LT_STR) + ? syslist->lval.lvstr.ls_ptr : NULL; + mdrname = (syslist->lnxt && syslist->lnxt->ltyp == LT_STR) + ? syslist->lnxt->lval.lvstr.ls_ptr : NULL; + } + if (!swxlinks) + printf(";;; System \"%s\", Directory \"%s\"\n", + sysname ? sysname : "", + mdrname ? mdrname : ""); + + /* Loop thru list of files */ + lcur = dlist->lnxt; + for (lcur = dlist->lnxt; lcur; lcur = lcur->lnxt) { + register struct lnode *l; + struct filent fe; + + l = (lcur->ltyp == LT_LIST) ? lcur->lval.lvl : NIL; + if (!l) { + printf("; Error - non-list element in directory list\n"); + continue; + } + if (!fesetup(&fe, l)) /* Set up filent from list spec */ + continue; + + /* Now munch results. For now, we just output a list of links. */ + if (swlinks) { + outlink(&fe); + } else + outfile(&fe); + } +} + + +int +fesetup(register struct filent *fe, + register struct lnode *l) +{ + fe->ffn1 = (l && l->ltyp == LT_STR) ? l->lval.lvstr.ls_ptr : NULL; + if (l) l = l->lnxt; + fe->ffn2 = (l && l->ltyp == LT_STR) ? l->lval.lvstr.ls_ptr : NULL; + if (l) l = l->lnxt; + fe->fslen = (l && l->ltyp == LT_ATOM) ? l->lval.lvstr.ls_ptr : NULL; + if (l) l = l->lnxt; + fe->fsbsz = (l && l->ltyp == LT_ATOM) ? l->lval.lvstr.ls_ptr : NULL; + if (l) l = l->lnxt; + fe->flink = (l && l->ltyp == LT_STR) ? l->lval.lvstr.ls_ptr : NULL; + if (l) l = l->lnxt; + fe->fscreat = (l && l->ltyp == LT_ATOM) ? l->lval.lvstr.ls_ptr : NULL; + if (l) l = l->lnxt; + fe->fsref = (l && l->ltyp == LT_ATOM) ? l->lval.lvstr.ls_ptr : NULL; + if (l) l = l->lnxt; + fe->fsauth = (l && l->ltyp == LT_STR) ? l->lval.lvstr.ls_ptr : NULL; + + /* Now derive other elements and/or authenticate numbers */ + fe->fldir = fe->flfn1 = fe->flfn2 = + fe->fpathname = NULL; + fe->flen = fe->fbsz = fe->fcreat = fe->fref = 0; + + if (fe->fslen) { + if (!sscanf(fe->fslen, "%li%*[.]", &fe->flen)) { + printf("; Bad length syntax: \"%s\"\n", fe->fslen); + return 0; + } + } + if (fe->fsbsz) { + if (!sscanf(fe->fsbsz, "%li%*[.]", &fe->fbsz)) { + printf("; Bad bytesize syntax: \"%s\"\n", fe->fsbsz); + return 0; + } + } + if (fe->fscreat) { + if (!sscanf(fe->fscreat, "%li%*[.]", &fe->fcreat)) { + printf("; Bad creation-date syntax: \"%s\"\n", fe->fscreat); + return 0; + } + } + if (fe->fsref) { + if (!sscanf(fe->fsref, "%li%*[.]", &fe->fref)) { + printf("; Bad reference-date syntax: \"%s\"\n", fe->fsref); + return 0; + } + } + + if (fe->flink) { + char *ldir, *lfn1, *lfn2; + static char lbuf[30]; + + if (strlen(fe->flink) >= sizeof(lbuf)-1) { + printf("; Target of link too long: \"%s\"\n", fe->flink); + return 0; + } + strcpy(lbuf, fe->flink); + ldir = lbuf; + lfn1 = strchr(lbuf, ';'); + if (lfn1) { + *lfn1++ = '\0'; + while (isspace(*lfn1)) ++lfn1; + lfn2 = lfn1; + while (!isspace(*lfn2)) ++lfn2; + *lfn2++ = '\0'; + while (isspace(*lfn2)) ++lfn2; + } + if (!lfn1 || !*ldir || !*lfn1 || !*lfn2) { + printf("; Bad target syntax: \"%s\"\n", fe->flink); + return 0; + } + fe->fldir = ldir; + fe->flfn1 = lfn1; + fe->flfn2 = lfn2; + } + + /* Construct local pathname. */ + if (!fe->flink) { + static char pbuf[6+1+6+1]; + register char *cp; + + if (!fe->ffn1 || !fe->ffn2) { + printf("; No FN1 or FN2 for filespec, skipping \"%s\" \"%s\"\n", + (fe->ffn1 ? fe->ffn1 : ""), + (fe->ffn2 ? fe->ffn2 : "") ); + return 0; + } + + if ((strlen(fe->ffn1) + strlen(fe->ffn1) + 2) > sizeof(pbuf)) { + printf("; FN1+FN2 too large, skipping \"%s\" \"%s\"\n", + (fe->ffn1 ? fe->ffn1 : ""), + (fe->ffn2 ? fe->ffn2 : "") ); + return 0; + } + + cp = its2unixfn(pbuf, fe->ffn1); + *cp++ = '.'; + cp = its2unixfn(cp, fe->ffn2); + *cp = '\0'; + fe->fpathname = pbuf; + } + + /* Translate author if possible?? */ + + return 1; +} + +/* Translate ITS filename to Unix version, using mapping of Alan Bawden, +** to wit: +** ITS Unix +** / => { +** Space => ~ +** A - Z => a - z +** . => _ +** _ => } +** All other chars are passed on unchanged. +*/ +char * +its2unixfn(register char *cp, + register char *itsfn) +{ + register int ch; + + while (ch = *itsfn++) { + switch (ch) { + case '/': ch = '{'; break; + case ' ': ch = '~'; break; + case '.': ch = '_'; break; + case '_': ch = '}'; break; + default: + if (isupper(ch)) + ch = tolower(ch); + break; + } + *cp++ = ch; + } + return cp; +} + +char * +fn6quot(register char *fn) +{ + static char cbuf[20]; + register char *fnp = fn; + register char *cp = cbuf; + register int c; + + while (c = *fnp++) { + switch (c) { + case ' ': c = '~'; break; +#if 0 + case '\\': + *cp++ = '\\'; + break; +#endif + default: + if (!isprint(c)) { + printf("; Bad char in filename component: \"%s\"\n", fn); + c = 0; + break; + } + break; + } + if (!c) break; + *cp++ = c; + if (cp >= &cbuf[sizeof(cbuf)-1]) { + printf("; Filename component too large: \"%s\"\n", fn); + break; + } + } + *cp = '\0'; + return cbuf; +} + +void +outlink(register struct filent *fe) +{ + if (! fe->flink) + return; + if (!mdrname || !fe->ffn1 || !fe->ffn2) { + printf("; Null source dir, fn1 or fn2, skipping link to \"%s\".\n", + fe->flink); + return; + } + if (!fe->fldir || !fe->flfn1 || !fe->flfn2) { + printf("; Null target dir, fn1 or fn2, skipping link to \"%s\".\n", + fe->flink); + return; + } + + if (swxlinks) + printf(":link %s;%s %s, %s;%s %s\n", + mdrname, fe->ffn1, fe->ffn2, + fe->fldir, fe->flfn1, fe->flfn2); + else + printf("ITSFILE: %s %-6s %-6s -> %s %s %s\n", + mdrname, fe->ffn1, fe->ffn2, + fe->fldir, fe->flfn1, fe->flfn2); +} + +void +outfile(register struct filent *fe) +{ + if (!fe->flink) { + /* Verify that file actually exists? */ + } + + printf("ITSFILE: %s", fn6quot(mdrname)); + printf(" %-6s", fn6quot(fe->ffn1)); + printf(" %-6s", fn6quot(fe->ffn2)); + + printf(" { %lu %lu %2lu %4lu", + fe->fcreat, fe->fref, fe->fbsz, fe->flen); + if (fe->fsauth) + printf(" %s", fn6quot(fe->fsauth)); + printf(" }"); + + if (fe->flink) { + printf(" -> %s", fn6quot(fe->fldir)); + printf(" %s", fn6quot(fe->flfn1)); + printf(" %s\n", fn6quot(fe->flfn2)); + } else { + printf(" u36 %s%s\n", + (swprefix ? swprefix : ""), fe->fpathname); + } +} + +/* LREAD.C - originally a separate file, but now incorporated directly + * for simplicity. + */ +/* Stuff for feeble-minded list processor */ + +#if 0 +#include +#include +#include /* For malloc */ +#include "lread.h" +#endif + +#define MAXLINE 300 + +static int llstch = -1; +static int leofflg; +#define unlrch(c) llstch = c + +static int lineno = 0; +static char linebuf[MAXLINE]; +static char *linecp = linebuf; +static int linecnt = 0; + +static struct lnode *lrstr(FILE *, int); +static void wspfls(FILE *); +static int lrch(FILE *); +static int islword(int); +static int ustrcmp(char *, char *); + + +struct lnode * +lread(register FILE *f) +{ + register int c; + register struct lnode *lp, *lp2; + struct lnode *head; + + wspfls(f); + if ((c = lrch(f))== EOF) + return NIL; + if (c == ')') /* End of a list? */ + return NIL; + if (c == '(') { /* Start of a list? */ + head = lp = lnget(); + lp->ltyp = LT_LIST; + if ((head->lval.lvl = lp = lread(f)) == NIL) + return head; /* Return empty list */ + while(lp2 = lread(f)) { + lp->lnxt = lp2; + lp = lp2; + } + return head; + } + + /* Atom of some kind */ + if (c=='"') + return lrstr(f, LT_STR); + unlrch(c); + lp = lrstr(f, LT_ATOM); + + /* Special check */ + if (lp && lp->lval.lvstr.ls_cnt == 3 + && ustrcmp(lp->lval.lvstr.ls_ptr, "NIL")==0) { + free(lp->lval.lvstr.ls_ptr); + lp->lval.lvstr.ls_ptr = NULL; + lp->ltyp = LT_NIL; + } + return lp; +} + + + +#define LSMAX 300 /* Max # chars in atom string */ +static struct lnode * +lrstr(register FILE *f, int type) +{ + register char *cp; + register int c, i; + struct lnode *lp; + char cbuf[LSMAX]; + + cp = cbuf; + i = 0; + + while((c = lrch(f)) != EOF) + if (type == LT_STR) switch(c) { + case '"': + if ((c = lrch(f)) == EOF) + return NIL; + if (c != '"') { + unlrch(c); + goto out; + } + default: + ok: + if (++i > LSMAX) + break; + *cp++ = c; + continue; + } + else { + if (islword(c)) goto ok; + unlrch(c); + break; + } + out: + *cp = 0; + lp = lnget(); + lp->ltyp = type; + lp->lval.lvstr.ls_cnt = i; + lp->lval.lvstr.ls_ptr = cp = (char *)malloc(i+1); + memcpy(lp->lval.lvstr.ls_ptr, cbuf, i); + cp[i] = '\0'; + return lp; +} + +static void +wspfls(register FILE *f) +{ + register int c; + + for (;;) { + c = lrch(f); + if (isspace(c)) continue; + if (c == ';') + while ((c = lrch(f)) != '\n') + if (c == EOF) return; + break; + } + if (c != EOF) unlrch(c); +} + +static int +lrch(register FILE *f) +{ + register int c; + + if ((c = llstch) >= 0) { + if (c == 0 && leofflg) + return EOF; + llstch = -1; + return c; + } + if ((c = getc(f)) == EOF) { + leofflg = 1; + llstch = 0; + *linecp = 0; + linecp = linebuf; + linecnt = 0; + return c; + } + if (c == '\n') { + lineno++; + linecp = linebuf; + linecnt = 0; + } else { + if (linecnt >= MAXLINE-1) { + linecp = linebuf; + linecnt = 0; + } + ++linecnt; + *linecp++ = c; + } + return c; +} + +static int +islword(int c) +{ + return (isgraph(c) + && c != '(' && c !=')' && c != ';' + && c != '"' && c != '\\'); +} + +struct lnode * +lnget(void) +{ + return (struct lnode *)calloc(1,sizeof(struct lnode)); +} + +/* Returns s1 - s2 (case-independent) +*/ +static int +ustrcmp(register char *s1, register char *s2) +{ + register int res; + + for (; *s1; ++s1, ++s2) { + if (*s1 != *s2) { + if (res = (islower(*s1) ? toupper(*s1) : *s1) + - (islower(*s2) ? toupper(*s2) : *s2)) + return res; /* Failed */ + } + } + return 0; +} + +void +lprint(FILE *f, struct lnode *alp) +{ + register struct lnode *lp; + + if (!alp) { + fprintf(f, "nil"); + return; + } + for (lp = alp; lp; lp = lp->lnxt) { + if (lp != alp) + fputc(' ', f); + switch (lp->ltyp) { + case LT_ATOM: + fprintf(f, "%s", lp->lval.lvstr.ls_ptr); + break; + case LT_STR: + fprintf(f, "\"%s\"", lp->lval.lvstr.ls_ptr); + break; + case LT_NIL: + fprintf(f, "NIL"); + break; + case LT_LIST: + fputc('(', f); + lprint(f, lp->lval.lvl); + fputc(')', f); + break; + + case LT_NUM: + default: + fprintf(f, "\n;; Unknown node type %d\n", lp->ltyp); + } + } +} diff --git a/src/uexbconv.c b/src/uexbconv.c new file mode 100644 index 0000000..fe3d3fa --- /dev/null +++ b/src/uexbconv.c @@ -0,0 +1,254 @@ +/* UEXBCONV.C - Convert .EXB file to .SAV +*/ +/* $Id: uexbconv.c,v 2.2 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: uexbconv.c,v $ + * Revision 2.2 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* The KL10 FE is a PDP-11 that stores its PDP-10 binaries, particularly + * the bootstraps "boot.exe" and "mtboot.exe", in a format called + * "RSX-BINARY". These files sometimes exist on the KL filesystem with + * the extension .EXB. + * + * The KL10 program RSXFMT.EXE converts from .SAV to .EXB but does not + * furnish the opposite conversion; hence this utility. + */ + +/* +BOOT.EXB is in what RSXFMT.EXE calls "RSX-Binary" (or RSB) +format, which stores 4 8-bit bytes as two PDP-11 words, one each +halfword per code at PUTBYT: + + Word: <00>,,<00> + +Actually the file is better thought of as a file of 18-bit bytes with +a PDP-11 word in each 18-bit byte. These PDP-11 words are organized +into records like so: + <16-bit count of 8-bit bytes> + + <1 padding byte if needed to get to 16-bit word boundary> + + +When transforming a SAV file into RSB format, the words are munched +and output as bytes in the following order by the code at SAVGRC: + +.SAV format => Internal => Output bytes + M Blocks of N bytes: +<-wc>,, => 0,, => hi 4 bits dropped + => => hi 4 in low 4 of B4 + +... + 1 Block of 4 bytes: +,, => 0,, => hi 4 bits dropped + +*/ +#include + +#include "word10.h" +#include "wfio.h" + +#include "wfio.c" /* To avoid linkage hassle */ + +int swverb = 0; +WFILE wfis, wfos; + +static int getbyt(WFILE *wf); +static int getcnt(WFILE *wf); +static int getaddr(WFILE *wf, long *ap); +static int getw10(WFILE *wf, w10_t *wp); + +static char usage[] = "\ +Usage: %s [-v] < infile.exb > outfile.sav\n"; + +main(int argc, char **argv) +{ + int n; + int wc; + long addr; + long saddr; + WFILE *wf = &wfis; + WFILE *owf = &wfos; + w10_t w; + char *arg; + + if (argc > 1) { + arg = argv[1]; + if (arg[0] != '-') { + fprintf(stderr, usage, argv[0]); + exit(1); + } + switch (arg[1]) { + case 'v': swverb = 1; break; + default: + fprintf(stderr, usage); + exit(1); + } + } + + wf_init(wf, WFT_C36, stdin); + wf_init(owf, WFT_C36, stdout); + + while ((n = getcnt(wf)) != EOF) { + gotn: + if (n < 4) { + fprintf(stderr, "Bad block, length %d < 4\n", n); + break; + } + if (getaddr(wf, &addr)) { + fprintf(stderr, "Unexpected EOF, incomplete addr\n"); + break; + } + if ((n -= 4) == 0) { + /* Start address */ + W10_XSET(w, 0254000 /* JRST */, addr & H10MASK); + wf_put(owf, w); + if (swverb) + fprintf(stderr, "START = %lo,,%lo\n", + (long)LHGET(w), (long)RHGET(w)); + if ((n = getcnt(wf)) == EOF) { + if (swverb) + fprintf(stderr, "DONE! EOF seen as expected\n"); + exit(0); + } + fprintf(stderr, "Warning - More data after start address = %d\n", + n); + goto gotn; + } + if ((n % 5) != 0) { + fprintf(stderr, "Word byte count not multiple of 5: %d\n", + n); + break; + } + wc = n/5; + W10_XSET(w, (-wc) & H10MASK, (addr-1) & H10MASK); + if (swverb) + fprintf(stderr, "10block = %lo,,%lo\n", + (long)LHGET(w), (long)RHGET(w)); + wf_put(owf, w); + while (--wc >= 0) { + if (getw10(wf, &w)) { + fprintf(stderr, "Unexpected EOF reading word, %d left\n", + (-wc)+1); + break; + } + wf_put(owf, w); + } + if (wc >= 0) break; + + if (n & 01) { + if (swverb) + fprintf(stderr, "Padding input 1 byte\n"); + (void)getbyt(wf); + } + + } + fprintf(stderr, "Aborted!\n"); + exit(1); +} + +static int getbyt(WFILE *wf) +{ + w10_t w; + static int bx = -1; + static unsigned char b[4]; + + switch (++bx) { + case 0: + if (!wf_get(wf, &w)) + return EOF; + if ((LHGET(w) & 0600000) || (RHGET(w) & 06000000)) { + fprintf(stderr, "MBZ bits set in word: %lo,,%lo\n", + (long)LHGET(w), (long)RHGET(w)); + return EOF; + } + b[0] = LHGET(w) & 0377; + b[1] = LHGET(w) >> 8; + b[2] = RHGET(w) & 0377; + b[3] = RHGET(w) >> 8; + + if (swverb) + fprintf(stderr, "InputWd = %lo,,%lo = %o %o %o %o\n", + (long)LHGET(w), (long)RHGET(w), + b[0], b[1], b[2], b[3]); + return b[0]; + case 1: + return b[1]; + case 2: + return b[2]; + case 3: + bx = -1; + return b[3]; + } + return EOF; +} + +static int getcnt(WFILE *wf) +{ + int b0, b1, cnt; + + if ((b0 = getbyt(wf)) == EOF) + return EOF; + if ((b1 = getbyt(wf)) == EOF) { + fprintf(stderr, "Bad EOF, 1-byte count\n"); + return EOF; + } + cnt = (b1<<8) | b0; + if (swverb) + fprintf(stderr, "Byteblock Count = %d\n", cnt); + return cnt; +} + +static int getaddr(WFILE *wf, long *ap) +{ + int i, c; + long addr = 0; + + for (i = 0; i < 4; ++i) { + if ((c = getbyt(wf)) == EOF) + return EOF; + addr |= ((long)c) << (8*i); + } + *ap = addr; + if (swverb) + fprintf(stderr, "Addr = %lo\n", addr); + return 0; +} + +static int getw10(WFILE *wf, w10_t *wp) +{ + int i, c; + w10_t w; + unsigned char b[5]; + + for (i = 0; i < 5; ++i) { + if ((c = getbyt(wf)) == EOF) + return EOF; + b[i] = c & 0xff; + } + W10_XSET(w, + ((b[4]&017)<<14) | (b[3]<<6) | (b[2]>>2), + ((b[2]&03)<<16) | (b[1]<<8) | b[0]); + *wp = w; + if (swverb) + fprintf(stderr, "Word = %lo,,%lo\n", + (long)LHGET(w), (long)RHGET(w)); + return 0; +} + diff --git a/src/vdisk.c b/src/vdisk.c new file mode 100644 index 0000000..b24b017 --- /dev/null +++ b/src/vdisk.c @@ -0,0 +1,1071 @@ +/* VDISK.C - Virtual Disk support routines +*/ +/* $Id: vdisk.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: vdisk.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#include +#include /* For malloc */ +#include +#include + +#include "rcsid.h" +#include "cenv.h" +#include "word10.h" +#include "osdsup.h" +#include "vdisk.h" + +#ifdef RCSID + RCSID(vdisk_c,"$Id: vdisk.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Define stuff for format conversions */ + +# define vdk_fmt(i,n,c,s,fr,to) \ + fr(w10_t *, int, unsigned char *), \ + to(unsigned char *, w10_t *, int) + +static void VDK_FORMATS; /* Automate function predecls */ +# undef vdk_fmt + +static struct { + char *fmt_name; /* Short name of format */ + int fmt_siz; /* # bytes in a double-word */ + void (*fmt_fr)( /* Conversion routine FROM format to words */ + w10_t *, int, unsigned char *); + void (*fmt_to)( /* Conversion routine TO format from words */ + unsigned char *, w10_t *, int); +} vdkfmttab[] = { +# define vdk_fmt(i,n,c,s,f,t) { n, s, f, t } + VDK_FORMATS +# undef vdk_fmt +}; + +/* Error reporting for all VDISK code. +** Currently uses a fixed-size error string buffer. This is dumb but +** simple; all calling routines must make some effort to keep length +** within bounds. +** Specifically, never use plain "%s" -- always limit it. +*/ +static void +vdkerror(register struct vdk_unit *d, char *fmt, ...) +{ + char ebuf[512]; + char *devnam = &d->dk_devname[0]; + + if (*devnam == 0) + devnam = "<\?\?\?>"; + { + va_list ap; + va_start(ap, fmt); + vsprintf(ebuf, fmt, ap); + va_end(ap); + } + + if (d->dk_errhan == NULL) + fprintf(stderr, "%s: %s\n", devnam, ebuf); + else + (*(d->dk_errhan))(d, ebuf); +} + + +int +vdk_init(register struct vdk_unit *d, + void (*errhdlr)(struct vdk_unit *, char *), + char *arg) +{ + memset((char *)d, 0, sizeof(*d)); /* For now */ + d->dk_errhan = errhdlr; + d->dk_errarg = arg; + return TRUE; +} + +int +vdk_mount(register struct vdk_unit *d, + char *path, + int wrtf) +{ + size_t cvtsiz; + + if (!d->dk_devname[0]) { + d->dk_err = EINVAL; /* Invalid arg */ + return FALSE; /* Not initialized */ + } + + /* Prepare some things that depend on format. + ** If doing conversion, the buffer pointed to by dk_buf needs to be big + ** enough to contain at least one sector of the largest possible format. + ** For now I'll default it to 1024 words as that's the largest OS page + ** unit (ITS) and should contain enough bytes to support even a bizarro + ** future sector format. + */ + + if ((0 <= d->dk_format) && (d->dk_format < VDK_FMT_N)) { + cvtsiz = 1024 * sizeof(w10_t); /* Default bufsiz */ + d->dk_bytesec = (VDK_NWDS(d) * vdkfmttab[d->dk_format].fmt_siz) / 2; + + if ((d->dk_fmt2wds = vdkfmttab[d->dk_format].fmt_fr) == cvtfr_raw) + d->dk_fmt2wds = NULL; + if ((d->dk_wds2fmt = vdkfmttab[d->dk_format].fmt_to) == cvtto_raw) + d->dk_wds2fmt = NULL; + + } else { + vdkerror(d, "vdk_mount: Unknown disk format %d", d->dk_format); + d->dk_err = EINVAL; + return FALSE; + } + + /* If doing any kind of conversions, will need buffer */ + if (d->dk_fmt2wds || d->dk_wds2fmt) { + if (!d->dk_buf || (d->dk_bufsiz < cvtsiz)) { + if (d->dk_buf) { + free(d->dk_buf); + d->dk_buf = NULL; + } + if (!(d->dk_buf = (unsigned char *)malloc(cvtsiz))) { + vdkerror(d, "vdk_mount: Cannot alloc cvt buffer of size %ld", + (long)cvtsiz); + d->dk_err = errno; + return FALSE; + } + d->dk_bufsiz = cvtsiz; + } + d->dk_bufsecs = d->dk_bufsiz / d->dk_bytesec; /* # sectors in buff */ + } + + +#if VDK_DISKMAP + if (d->dk_ismap) { + memset((char *)d->dk_dfh, 0, sizeof(struct vdk_header)); + d->dk_blkalign = -1; /* Round down, ignore extra sectors */ + d->dk_secblk = 1; /* Blocking factor: # sectors per block */ + + d->dk_wdsblk = d->dk_secblk * d->dk_nwds; /* # words per block */ + d->dk_byteblk = d->dk_secblk * d->dk_bytesec; /* # bytes per block */ + d->dk_blkcyl = (uint32)d->dk_nsecs * d->dk_ntrks; + + /* Temporarily blkcyl is # sectors per cylinder */ + if (d->dk_blkalign < 0) { + d->dk_blkcyl /= d->dk_secblk; /* Round down */ + d->dk_nblks = d->dk_blkcyl * d->dk_ncyls; + } else if (d->dk_blkalign > 0) { /* Round up */ + d->dk_blkcyl = (d->dk_blkcyl + d->dk_secblk - 1) / d->dk_secblk; + d->dk_nblks = d->dk_blkcyl * d->dk_ncyls; + } else { /* Don't round */ + d->dk_nblks = (d->dk_blkcyl * d->dk_ncyls) / d->dk_secblk; + d->dk_blkcyl = 0; /* Shouldn't need this any more */ + } + + d->dk_map = NULL; + d->dk_freep = 0; + } +#endif /* VDK_DISKMAP */ + + + /* Actually open the real device! */ + d->dk_iswrite = wrtf; /* See if writing */ + if (!os_fdopen(&d->dk_fd, path, (wrtf ? "+b" : "rb"))) { + /* Diskfile doesn't appear to exist, try to create it */ + fprintf(stderr, "[Creating %s disk file \"%s\"]\r\n", + d->dk_devname, path); + if (!wrtf || !os_fdopen(&d->dk_fd, path, "+bc")) { + vdkerror(d, "vdk_mount: Cannot create %s disk file \"%s\"", + d->dk_devname, path); + d->dk_err = errno; + return FALSE; + } +#if VDK_DISKMAP + if (d->dk_ismap && !vdk_mapcreate(d)) { + vdkerror(d, "vdk_mount: Cannot create disk map"); + /* mapcreate should set dkerr */ + return FALSE; + } +#endif + } +#if VDK_DISKMAP + else if (d->dk_ismap) { + if (!vdk_mapload(d)) { + vdkerror(d, "vdk_mount: Cannot load disk map"); + /* mapload should set dkerr */ + return FALSE; + } + } +#endif + + /* Success, remember the filename */ + if (!(d->dk_filename = (char *)malloc(strlen(path)+1))) { + vdkerror(d, "vdk_mount: Cannot malloc pathname \"%s\"", path); + d->dk_err = errno; + os_fdclose(d->dk_fd); + return FALSE; + } + strcpy(d->dk_filename, path); + + return TRUE; +} + +int +vdk_unmount(register struct vdk_unit *d) +{ + if (d->dk_filename) { +#if VDK_DISKMAP + if (d->dk_ismap) { + if (!vdk_unmap(d)) + return 0; + } +#endif + if (!os_fdclose(d->dk_fd)) + return 0; + free(d->dk_filename); + d->dk_filename = NULL; + } + return 1; +} + +/* Read from disk. +** Return # sectors read. +** +** Raw (no-conversion) case reads directly to word buffer. +** Conversion cases will use intermediate buffer. +** Too dangerous to do conversion-in-place as CPU and disk are +** now independent threads/processes and CPU shouldn't ever see +** the intermediate forms! +*/ +int +vdk_read(register struct vdk_unit *d, + w10_t *wp, /* Word buffer to read data */ + int32 secaddr, /* Sector addr on disk */ + int nsec) /* # sectors - Never more than 16 bits */ +{ +#if VDK_DISKMAP + register osdaddr_t dwaddr; + + if (d->dk_format != VDK_FMT_RAW) { + vdkerror(d, "vdk_read: Can't map format %d", d->dk_format); + return 0; + } + dwaddr = secaddr * VDK_NWDS(d); + if (dwaddr % d->dk_wdsblk) { + vdkerror(d, "vdk_read: Non-sector disk address %ld.", (long) dwaddr); + return 0; /* Later do something better */ + } + return vdk_mapio(d, 0, dwaddr, wp, (int)(nsec * VDK_NWDS(d))) + +#else + register osdaddr_t daddr; + register size_t bcnt; + size_t ndone = 0; + int secleft; + + d->dk_err = 0; + + if (d->dk_fmt2wds == NULL) { /* RAW input? (No conversion) */ + + daddr = secaddr * VDK_NWDS(d) * sizeof(w10_t); + bcnt = nsec * VDK_NWDS(d) * sizeof(w10_t); + + if (!os_fdseek(d->dk_fd, daddr)) { + d->dk_err = errno; /* OS DEP!! */ + vdkerror(d, "vdk_read: seek failed for %ld, errno = %d", + daddr, errno); + return 0; /* Later do something better? */ + } + + if (!os_fdread(d->dk_fd, (char *)wp, bcnt, &ndone)) { + d->dk_err = errno; /* OS DEP!! */ + vdkerror(d, "vdk_read: failed: cnt %ld, ret %ld, errno = %d", + bcnt, ndone, errno); + return ndone / (VDK_NWDS(d) * sizeof(w10_t)); + } + + if (ndone < bcnt) { /* If incomplete read, sector may not exist */ + /* SPECIAL HACK FOR SPARSE NORMAL FILES + ** Fill out unread words with zeros, assuming sectors haven't yet + ** been created on disk and we ran into EOF. + */ + memset(((char *)wp)+ndone, 0, bcnt - ndone); + } + return nsec; + } + + /* Any other kind of format comes here -- requires using another buffer. + ** Speed is not of the essence for the following cases. + ** An inner loop is used because the buffer may not be large enough to + ** hold the entire transfer requested. It will always, however, be large + ** enough to hold at least one sector at a time. + */ + + /* Set up for OS I/O */ + daddr = secaddr * d->dk_bytesec; /* Disk addr in bytes */ + if (!os_fdseek(d->dk_fd, daddr)) { + d->dk_err = errno; /* OS DEP!! */ + vdkerror(d, "vdk_read: seek failed for %ld., errno = %d", + daddr, errno); + return 0; + } + + secleft = nsec; + while (secleft > 0) { + int err; + int secwant, secdone; + + secwant = (secleft <= d->dk_bufsecs) ? secleft : d->dk_bufsecs; + bcnt = secwant * d->dk_bytesec; /* # bytes to read */ + + err = !os_fdread(d->dk_fd, (char *) d->dk_buf, bcnt, &ndone); + + /* Find # sectors read in (ie need conversion) */ + secdone = (ndone == bcnt) ? secwant : (ndone / d->dk_bytesec); + if (secdone) { + (*d->dk_fmt2wds)(wp, (int)(secdone * VDK_NWDS(d)), d->dk_buf); + secleft -= secdone; + wp += secdone * VDK_NWDS(d); + } + + if (err) { + d->dk_err = errno; /* OS DEP!! */ + vdkerror(d, "vdk_read: failed, cnt %ld., ret %ld., errno = %d", + bcnt, ndone, errno); + break; + } + + if (secdone < secwant) { + /* SPECIAL HACK FOR SPARSE NORMAL FILES + ** Fill out unread words with zeros, assuming sectors haven't yet + ** been created on disk and we ran into EOF. + */ + memset((char *)wp, 0, secleft * VDK_NWDS(d) * sizeof(w10_t)); + /* wp += secleft * VDK_NWDS(d); */ + secleft = 0; + break; + } + } + + return nsec - secleft; +#endif /* !VDK_DISKMAP */ +} + +/* Write to disk. +** Return # sectors written. +** +** Raw (no-conversion) case writes directly from word buffer. +** Conversion cases use intermediate buffer. +*/ +int +vdk_write(register struct vdk_unit *d, + w10_t *wp, /* Word buffer to read data */ + int32 secaddr, /* Sector addr on disk */ + int nsec) /* # sectors - Never more than 16 bits */ +{ +#if VDK_DISKMAP + register osdaddr_t dwaddr; + + if (d->dk_format != VDK_FMT_RAW) { + vdkerror(d, "vdk_write: Can't map format %d", d->dk_format); + return 0; + } + dwaddr = secaddr * VDK_NWDS(d); + if (dwaddr % d->dk_wdsblk) { + vdkerror(d, "vdk_write: Non-sector disk address %ld.", (long) dwaddr); + return 0; /* Later do something better */ + } + return vdk_mapio(d, TRUE, dwaddr, wp, (int)(nsec * VDK_NWDS(d))) + +#else + register osdaddr_t daddr; + register size_t bcnt; + size_t ndone = 0; + int secleft; + + d->dk_err = 0; + + if (d->dk_wds2fmt == NULL) { /* RAW output? (No conversion) */ + + daddr = secaddr * VDK_NWDS(d) * sizeof(w10_t); + bcnt = nsec * VDK_NWDS(d) * sizeof(w10_t); + + if (!os_fdseek(d->dk_fd, daddr)) { + vdkerror(d, "vdk_write: seek failed for %ld, errno = %d", + daddr, errno); + d->dk_err = errno; /* OS DEP!! */ + return 0; /* Later do something better? */ + } + + if (!os_fdwrite(d->dk_fd, (char *)wp, bcnt, &ndone)) { + vdkerror(d, "vdk_write: failed: cnt %ld, ret %ld, errno = %d", + bcnt, ndone, errno); + d->dk_err = errno; /* OS DEP!! */ + return ndone / (VDK_NWDS(d) * sizeof(w10_t)); + } + return nsec; + } + + /* Any other kind of format comes here -- may require using another buffer. + ** Speed is not of the essence for the following cases. + ** An inner loop is used because the buffer may not be large enough to + ** hold the entire transfer requested. It will always, however, be large + ** enough to hold at least one sector at a time. + */ + + /* Set up for OS I/O */ + daddr = secaddr * d->dk_bytesec; /* Disk addr in bytes */ + if (!os_fdseek(d->dk_fd, daddr)) { + vdkerror(d, "vdk_read: seek failed for %ld., errno = %d", + daddr, errno); + d->dk_err = errno; /* OS DEP!! */ + return 0; + } + + secleft = nsec; + while (secleft > 0) { + int err; + int secwant, secdone; + + secwant = (secleft <= d->dk_bufsecs) ? secleft : d->dk_bufsecs; + + /* Convert words into buffer */ + (*d->dk_wds2fmt)(d->dk_buf, wp, (int)(secwant * VDK_NWDS(d))); + + bcnt = secwant * d->dk_bytesec; /* # bytes to write */ + + err = !os_fdwrite(d->dk_fd, (char *) d->dk_buf, bcnt, &ndone); + + /* Find # sectors written */ + secdone = (ndone == bcnt) ? secwant : (ndone / d->dk_bytesec); + if (secdone) { + secleft -= secdone; + wp += secdone * VDK_NWDS(d); + } + + if (err || (secdone != secwant)) { + d->dk_err = errno; /* OS DEP!!! */ + vdkerror(d, "vdk_write: failed, cnt %ld., ret %ld., errno = %d", + bcnt, ndone, errno); + break; + } + } + + return nsec - secleft; +#endif /* !VDK_DISKMAP */ +} + +/* Format conversion routines */ + +/* + dbd9: Disk_BigEnd_Double (9/2) - Same as H36 (Tape_Hidens) + B0 1 2 3 4 5 6 7 + 8 9 10 11 12 13 14 15 [Word 0] + 16 17 18 19 20 21 22 23 + 24 25 26 27 28 29 30 31 + 32 33 34 35 B0 1 2 3 + 4 5 6 7 8 9 10 11 [Word 1] + 12 13 14 15 16 17 18 19 + 20 21 22 23 24 25 26 27 + 28 29 30 31 32 33 34 35 + +*/ +static void +cvtfr_dbd9(register w10_t *wp, + register int wcnt, + register unsigned char *ucp) +{ + register w10_t w; + register int dwcnt = wcnt >> 1; + + for (; --dwcnt >= 0; ucp += 9) { + LRHSET(w, + (((ucp[0]&0377)<<10) | ((ucp[1]&0377)<<2) | ((ucp[2]>>6)&03)), + (((ucp[2]&077)<<12) | ((ucp[3]&0377)<<4) | ((ucp[4]>>4)&017))); + *wp++ = w; + LRHSET(w, + (((ucp[4]&017)<<14) | ((ucp[5]&0377)<<6) | ((ucp[6]>>2)&077)), + (((ucp[6]&03)<<16) | ((ucp[7]&0377)<<8) | (ucp[8]&0377))); + *wp++ = w; + } + + /* Ugh, allow gobbling an odd word for generality */ + if (wcnt & 01) { + LRHSET(w, + (((ucp[0]&0377)<<10) | ((ucp[1]&0377)<<2) | ((ucp[2]>>6)&03)), + (((ucp[2]&077)<<12) | ((ucp[3]&0377)<<4) | ((ucp[4]>>4)&017))); + *wp++ = w; + } +} + +static void +cvtto_dbd9(register unsigned char *ucp, + register w10_t *wp, + register int wcnt) +{ + register w10_t w, w2; + register int dwcnt = wcnt >> 1; + + for (; --dwcnt >= 0; ) { + w = *wp++; + w2 = *wp++; + *ucp++ = (LHGET(w) >> 10) & 0377; + *ucp++ = (LHGET(w) >> 2) & 0377; + *ucp++ = ((LHGET(w)&03)<<6) | ((RHGET(w) >> 12) & 077); + *ucp++ = (RHGET(w) >> 4) & 0377; + *ucp++ = ((RHGET(w)&017)<<4) | ((LHGET(w2) >> 14) & 017); + *ucp++ = (LHGET(w2) >> 6) & 0377; + *ucp++ = ((LHGET(w2)&077)<<2) | ((RHGET(w2) >> 16) & 03); + *ucp++ = (RHGET(w2) >> 8) & 0377; + *ucp++ = RHGET(w2) & 0377; + } + + /* Ugh, allow writing an odd word for generality */ + if (wcnt & 01) { + w = *wp++; + *ucp++ = (LHGET(w) >> 10) & 0377; + *ucp++ = (LHGET(w) >> 2) & 0377; + *ucp++ = ((LHGET(w)&03)<<6) | ((RHGET(w) >> 12) & 077); + *ucp++ = (RHGET(w) >> 4) & 0377; + *ucp++ = ((RHGET(w)&017)<<4); + } +} + + +/* + dld9: Disk_LittleEnd_Double (9/2) + 28 29 30 31 32 33 34 35 + 20 21 22 23 24 25 26 27 + 12 13 14 15 16 17 18 19 + 4 5 6 7 8 9 10 11 [Word 0] + 32 33 34 35 B0 1 2 3 + 24 25 26 27 28 29 30 31 + 16 17 18 19 20 21 22 23 + 8 9 10 11 12 13 14 15 [Word 1] + B0 1 2 3 4 5 6 7 +*/ +static void +cvtfr_dld9(register w10_t *wp, + register int wcnt, + register unsigned char *ucp) +{ + register w10_t w; + register int dwcnt = wcnt >> 1; + + for (; --dwcnt >= 0; ucp += 9) { + LRHSET(w, + (((ucp[4]&017)<<14) | ((ucp[3]&0377)<<6) | ((ucp[2]>>2)&077)), + (((ucp[2]&03)<<16) | ((ucp[1]&0377)<<8) | (ucp[0]&0377))); + *wp++ = w; + LRHSET(w, + (((ucp[8]&0377)<<10) | ((ucp[7]&0377)<<2) | ((ucp[6]>>6)&03)), + (((ucp[6]&077)<<12) | ((ucp[5]&0377)<<4) | ((ucp[4]>>4)&017))); + *wp++ = w; + } + + /* Ugh, allow gobbling an odd word for generality */ + if (wcnt & 01) { + LRHSET(w, + (((ucp[4]&017)<<14) | ((ucp[3]&0377)<<6) | ((ucp[2]>>2)&077)), + (((ucp[2]&03)<<16) | ((ucp[1]&0377)<<8) | (ucp[0]&0377))); + *wp++ = w; + } +} + +static void +cvtto_dld9(register unsigned char *ucp, + register w10_t *wp, + register int wcnt) +{ + register w10_t w, w2; + register int dwcnt = wcnt >> 1; + + for (; --dwcnt >= 0; ) { + w = *wp++; + w2 = *wp++; + *ucp++ = RHGET(w) & 0377; + *ucp++ = (RHGET(w) >> 8) & 0377; + *ucp++ = ((LHGET(w)&077)<<2) | ((RHGET(w) >> 16) & 03); + *ucp++ = (LHGET(w) >> 6) & 0377; + *ucp++ = ((RHGET(w2)&017)<<4) | ((LHGET(w) >> 14) & 017); + *ucp++ = (RHGET(w2) >> 4) & 0377; + *ucp++ = ((LHGET(w2)&03)<<6) | ((RHGET(w2) >> 12) & 077); + *ucp++ = (LHGET(w2) >> 2) & 0377; + *ucp++ = (LHGET(w2) >> 10) & 0377; + } + + /* Ugh, allow writing an odd word for generality */ + if (wcnt & 01) { + w = *wp++; + *ucp++ = RHGET(w) & 0377; + *ucp++ = (RHGET(w) >> 8) & 0377; + *ucp++ = ((LHGET(w)&077)<<2) | ((RHGET(w) >> 16) & 03); + *ucp++ = (LHGET(w) >> 6) & 0377; + *ucp++ = (LHGET(w) >> 14) & 017; + } +} + + +/* + dbw8: Disk_BigEnd_Word (8) + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 + 0 0 0 0 B0 1 2 3 + 4 5 6 7 8 9 10 11 + 12 13 14 15 16 17 18 19 + 20 21 22 23 24 25 26 27 + 28 29 30 31 32 33 34 35 +*/ +static void +cvtfr_dbw8(register w10_t *wp, + register int wcnt, + register unsigned char *ucp) +{ + register w10_t w; + + for (; --wcnt >= 0; ucp += 8) { + LRHSET(w, + (((ucp[3]&017)<<14) | ((ucp[4]&0377)<<6) | ((ucp[5]>>2)&077)), + (((ucp[5]&03)<<16) | ((ucp[6]&0377)<<8) | (ucp[7]&0377))); + *wp++ = w; + } +} + +static void +cvtto_dbw8(register unsigned char *ucp, + register w10_t *wp, + register int wcnt) +{ + register w10_t w; + + for (; --wcnt >= 0; ) { + w = *wp++; + *ucp++ = 0; + *ucp++ = 0; + *ucp++ = 0; + *ucp++ = (LHGET(w) >> 14) & 017; + *ucp++ = (LHGET(w) >> 6) & 0377; + *ucp++ = ((LHGET(w)&077)<<2) | ((RHGET(w) >> 16) & 03); + *ucp++ = (RHGET(w) >> 8) & 0377; + *ucp++ = RHGET(w) & 0377; + } +} + +/* + dlw8: Disk_LittleEnd_Word (8) + 28 29 30 31 32 33 34 35 + 20 21 22 23 24 25 26 27 + 12 13 14 15 16 17 18 19 + 4 5 6 7 8 9 10 11 + 0 0 0 0 B0 1 2 3 + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 +*/ +static void +cvtfr_dlw8(register w10_t *wp, + register int wcnt, + register unsigned char *ucp) +{ + register w10_t w; + + for (; --wcnt >= 0; ucp += 8) { + LRHSET(w, + (((ucp[4]&017)<<14) | ((ucp[3]&0377)<<6) | ((ucp[2]>>2)&077)), + (((ucp[2]&03)<<16) | ((ucp[1]&0377)<<8) | (ucp[0]&0377))); + *wp++ = w; + } +} + +static void +cvtto_dlw8(register unsigned char *ucp, + register w10_t *wp, + register int wcnt) +{ + register w10_t w; + + for (; --wcnt >= 0; ) { + w = *wp++; + *ucp++ = RHGET(w) & 0377; + *ucp++ = (RHGET(w) >> 8) & 0377; + *ucp++ = ((LHGET(w)&077)<<2) | ((RHGET(w) >> 16) & 03); + *ucp++ = (LHGET(w) >> 6) & 0377; + *ucp++ = (LHGET(w) >> 14) & 017; + *ucp++ = 0; + *ucp++ = 0; + *ucp++ = 0; + } +} + +/* + dbh4: Disk_BigEnd_Halfword (8) + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 B0 1 + 2 3 4 5 6 7 8 9 + 10 11 12 13 14 15 16 17 + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 18 19 + 20 21 22 23 24 25 26 27 + 28 29 30 31 32 33 34 35 +*/ +static void +cvtfr_dbh4(register w10_t *wp, + register int wcnt, + register unsigned char *ucp) +{ + register w10_t w; + + for (; --wcnt >= 0; ucp += 8) { + LRHSET(w, (((ucp[1]&03)<<16) | ((ucp[2]&0377)<<8) | (ucp[3]&0377)), + (((ucp[5]&03)<<16) | ((ucp[6]&0377)<<8) | (ucp[7]&0377))); + *wp++ = w; + } +} + +static void +cvtto_dbh4(register unsigned char *ucp, + register w10_t *wp, + register int wcnt) +{ + register w10_t w; + + for (; --wcnt >= 0; ) { + w = *wp++; + *ucp++ = 0; + *ucp++ = (LHGET(w) >> 16) & 03; + *ucp++ = (LHGET(w) >> 8) & 0377; + *ucp++ = LHGET(w) & 0377; + *ucp++ = 0; + *ucp++ = (RHGET(w) >> 16) & 03; + *ucp++ = (RHGET(w) >> 8) & 0377; + *ucp++ = RHGET(w) & 0377; + } +} + +/* + dlh4: Disk_LittleEnd_Halfword (8) + 28 29 30 31 32 33 34 35 + 20 21 22 23 24 25 26 27 + 0 0 0 0 0 0 18 19 + 0 0 0 0 0 0 0 0 + 10 11 12 13 14 15 16 17 + 2 3 4 5 6 7 8 9 + 0 0 0 0 0 0 B0 1 + 0 0 0 0 0 0 0 0 + +*/ +static void +cvtfr_dlh4(register w10_t *wp, + register int wcnt, + register unsigned char *ucp) +{ + register w10_t w; + + for (; --wcnt >= 0; ucp += 8) { + LRHSET(w, (((ucp[6]&03)<<16) | ((ucp[5]&0377)<<8) | (ucp[4]&0377)), + (((ucp[2]&03)<<16) | ((ucp[1]&0377)<<8) | (ucp[0]&0377))); + *wp++ = w; + } +} + +static void +cvtto_dlh4(register unsigned char *ucp, + register w10_t *wp, + register int wcnt) +{ + register w10_t w; + + for (; --wcnt >= 0; ) { + w = *wp++; + *ucp++ = RHGET(w) & 0377; + *ucp++ = (RHGET(w) >> 8) & 0377; + *ucp++ = (RHGET(w) >> 16) & 03; + *ucp++ = 0; + *ucp++ = LHGET(w) & 0377; + *ucp++ = (LHGET(w) >> 8) & 0377; + *ucp++ = (LHGET(w) >> 16) & 03; + *ucp++ = 0; + } +} + + +/* "Rare" semi-raw conversions - no byte shuffling, but data is masked. +*/ +static void +cvtfr_rare(register w10_t *wp, + register int wcnt, + register unsigned char *ucp) +{ + register w10_t w, *frwp = (w10_t *)ucp; + + while (--wcnt >= 0) { + XWDSET(w, LHPGET(frwp)&H10MASK, RHPGET(frwp)&H10MASK); + ++frwp; + *wp++ = w; + } +} + +static void +cvtto_rare(register unsigned char *ucp, + register w10_t *wp, + register int wcnt) +{ + register w10_t w, *towp = (w10_t *)ucp; + + while (--wcnt >= 0) { + XWDSET(w, LHPGET(wp)&H10MASK, RHPGET(wp)&H10MASK); + ++wp; + *towp++ = w; + } +} + + +/* Raw no-op conversions. +** Mainly here so conversion table can be filled out with meaningful +** vectors for no-conversion cases. Shouldn't actually be called. +*/ +static void +cvtfr_raw(register w10_t *wp, + register int wcnt, + register unsigned char *ucp) +{ + memcpy((char *)wp, (char *)ucp, wcnt * sizeof(w10_t)); +} + +static void +cvtto_raw(register unsigned char *ucp, + register w10_t *wp, + register int wcnt) +{ + memcpy((char *)ucp, (char *)wp, wcnt * sizeof(w10_t)); +} + +/* Disk-map specific support */ + +#if VDK_DISKMAP + +static int +vdk_mapcreate(register struct vdk_unit *d) +{ + register osdaddr_t da; + register size_t mapsiz; + size_t err; + + /* Derive map config params from blocking factor */ +#define MAPINIT \ + d->dk_byteblk = (uint32) d->dk_secblk * d->dk_bytesec; /* bytes/blk */\ + d->dk_nblks = (((uint32) d->dk_nsecs ) * d-dk_ncyls; \ + d->dk_nblks /= d->dk_secblk; /* Find # blocks this disk */ + + mapsiz = d->dk_nblks * sizeof(osdaddr_t); + d->dk_map = (osdaddr_t *)calloc((size_t)d->dk_nblks, sizeof(osdaddr_t)); + if (!d->dk_map) { + vdkerror(d, "vdk_mapcreate: Malloc failed for map of %ld entries", + (long) d->dk_nblks); + return 0; + } + + /* Compute first free disk addr after header and map written. + ** Round up to first block. + */ + da = sizeof(struct vdk_header) + mapsiz; + da = ((da + d->dk_byteblk-1) / d->dk_byteblk) * d->dk_byteblk; + + /* Fill out initial disk header */ + d->dk_dfh.dh_freep = da; /* First free realdisk address */ + + /* Now initialize the diskfile. Assume just created, so at start. */ + if (!os_fdwrite(d->dk_fd, (char *) &(d->dk_dfh), sizeof(struct vdk_header), &err) + || err != sizeof(struct vdk_header)) { + vdkerror(d, "vdk_mapcreate: write failed for cnt %ld., errno = %d", + (long)sizeof(struct vdk_header), errno); + return 0; + } + if (!os_fdwrite(d->dk_fd, (char *) d->dk_map, mapsiz, &err) + || err != mapsiz) { + vdkerror(d, "vdk_mapcreate: write failed for cnt %ld., errno = %d", + (long)mapsiz, errno); + return 0; + } + return 1; /* Won! */ +} + +static int +vdk_mapload(register struct vdk_unit *d) +{ + register osdaddr_t da, *dp; + register size_t mapsiz; + size_t err; + + if (d->dk_map) { + vdkerror(d, "vdk_mapload: Disk already mapped"); + return 0; + } + + /* Read in diskfile header */ + if (!os_fdseek(d->dk_fd, (osdaddr_t)0)) { + vdkerror(d, "vdk_mapload: seek failed for BOF"); + return 0; + } + if (!os_fdread(d->dk_fd, (char *) &(d->dk_dfh), sizeof(struct vdk_header), &err) + || err != sizeof(struct vdk_header)) { + vdkerror(d, "vdk_mapload: read failed for cnt %ld., errno = %d", + (long)sizeof(struct vdk_header), errno); + return 0; + } + + /* Now should check veracity of header and set up remaining stuff + ** based on the config info it contains. + */ +#if 0 + /* Nothing for now */ +#endif + MAPINIT /* Do same config setup as mapcreate for now */ + + mapsiz = d->dk_nblks * sizeof(osdaddr_t); + d->dk_map = (osdaddr_t *)malloc(mapsiz); + if (!d->dk_map) { + vdkerror(d, "vdk_mapload: Malloc failed for map of %ld entries", + (long) d->dk_nblks); + return 0; + } + + if (!os_fdread(d->dk_fd, (char *) d->dk_map, mapsiz, &err) + || err != mapsiz) { + vdkerror(d, "vdk_mapload: read failed for cnt %ld., errno = %d", + (long)mapsiz, errno); + return 0; + } + + /* Now scan the map to determine the largest known block address, + ** hence the first free one. + ** Slogging through this at startup lets us avoid updating a + ** disk-resident variable at every write. + */ + da = 0; + dp = d->dk_map; + for (mapsiz = d->dk_nblks; mapsiz > 0; --mapsiz, ++dp) { + if (*dp > da) + da = *dp; + } + d->dk_freep = da + d->dk_byteblk; /* Point one block past highest */ + + return 1; /* Won! */ +} + + +static int +vdk_mapio(struct vdk_unit *d, + int wrtf, + osdaddr_t dwaddr, + w10_t *wp, + int wcnt) +{ + register uint32 blkno; + register osdaddr_t da; + register int bcnt; + register size_t bytes; + size_t err; + + blkno = dwaddr / d->dk_wdsblk; + + if (wcnt) for (;;) { + if ((bcnt = wcnt) > d->dk_wdsblk) + bcnt = d->dk_wdsblk; + bytes = bcnt * sizeof(w10_t); + if (blkno >= d->dk_nblks) { + vdkerror(d, "vdk_mapread: Non-ex block %ld.", (long)blkno); + return 0; + } + d->dk_mupdate = FALSE; + if (!(da = d->dk_map[blkno])) { /* Block exists in map? */ + if (!wrtf) { + /* Nope, just pretend we read a bunch of zeros */ + memset((char *)vp, 0, bytes); + goto rdone; + } else { + d->dk_mupdate = TRUE; + da = d->dk_freep; + } + } + if (!os_fdseek(d->dk_fd, da)) { + vdkerror(d, "vdk_mapread: seek failed for %ld., errno = %d", + (long)da, errno); + return 0; /* Later do something better */ + } + if (!(wrtf + ? os_fdwrite(d->dk_fd, (char *)vp, bytes, &err) + ? os_fdread(d->dk_fd, (char *)vp, bytes, &err))) { + vdkerror(d, "vdk_mapio: %s failed for cnt %ld., ret %ld., errno = %d", + wrtf ? "write" : "read", + (long)(bytes), (long)err, errno); + return 0; /* Later do something better */ + } + if (err != bytes) { +#if 0 + if (!wrtf && err == 0) { /* Check for read of non-ex data */ + memset((char *)vp, 0, bytes); + goto rdone; + } +#endif + vdkerror(d, "vdk_mapio: r/w failed for cnt %ld., ret %ld., errno = %d", + (long)bytes, (long)err, errno); + return 0; /* Later do something better */ + } + if (d->dk_mupdate) { + /* Successful write, update the map */ + if (bcnt < d->dk_wdsblk) { + /* Fill out all of incompletely written block */ + bytes = sizeof(w10_t)*(d->dk_wdsblk - bcnt); + memset(d->dk_blkbuf, 0, bytes); + if (!os_fdwrite(d->dk_fd, d->dk_blkbuf, bytes, &err)) { + vdkerror(d, "vdk_mapio: w failed for cnt %ld., ret %ld., errno = %d", + (long)bytes, (long)err, errno); + return 0; + } + } + + da = sizeof(struct vdk_header) + (blkno * sizeof(osdaddr_t)); + if (!os_fdseek(d->dk_fd, da)) { + vdkerror(d, "vdk_mapio: seek failed, map update at %ld., errno = %d", + (long)da, errno); + return 0; + } + if (!os_fdwrite(d->dk_fd, (char *)&(d->dk_freep), + sizeof(osdaddr_t), &err)) { + vdkerror(d, "vdk_mapio: w failed for cnt %ld., ret %ld., errno = %d", + (long)sizeof(osdaddr_t), (long)err, errno); + return 0; + } + + /* Update on disk won, so now update mem */ + d->dk_map[blkno] = d->dk_freep; + d->dk_freep = d->dk_byteblk; + } + + rdone: + if (wcnt <= bcnt) + break; /* Done, leave now */ + wcnt -= bcnt; + vp += bcnt; + blkno++; + } + return 1; +} + +#endif /* VDK_DISKMAP */ diff --git a/src/vdisk.h b/src/vdisk.h new file mode 100644 index 0000000..3aff047 --- /dev/null +++ b/src/vdisk.h @@ -0,0 +1,161 @@ +/* VDISK.H - Virtual Disk support definitions +*/ +/* $Id: vdisk.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: vdisk.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#ifndef VDISK_INCLUDED /* Only include once */ +#define VDISK_INCLUDED 1 + +#ifdef RCSID + RCSID(vdisk_h,"$Id: vdisk.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +/* Canonical C true/false values */ +#ifndef TRUE +# define TRUE 1 +#endif +#ifndef FALSE +# define FALSE 0 +#endif + +#ifndef VDK_DISKMAP /* Set TRUE to include disk mapping code */ +# ifdef KLH10_DISKMAP +# define VDK_DISKMAP KLH10_DISKMAP +# else +# define VDK_DISKMAP 0 +# endif +#endif + +#ifndef VDK_SECTOR_SIZE /* Allow specifying sector size in wds */ +# define VDK_SECTOR_SIZE 128 /* Default for all known DEC disks */ +# define VDK_NWDS(d) VDK_SECTOR_SIZE /* Size as function of disk unit */ +#endif + +#define VDK_FORMATS \ + vdk_fmt(VDK_FMT_RAW, "RAW", "Raw - no conversion", \ + 2*sizeof(w10_t), cvtfr_raw, cvtto_raw), \ + vdk_fmt(VDK_FMT_RARE, "RARE", "Rare - input clean, output raw", \ + 2*sizeof(w10_t), cvtfr_rare, cvtto_raw),\ + vdk_fmt(VDK_FMT_DBD9, "DBD9", "Disk_BigEnd_Double (9/2) (H36)", \ + 9, cvtfr_dbd9, cvtto_dbd9), \ + vdk_fmt(VDK_FMT_DLD9, "DLD9", "Disk_LittleEnd_Double (9/2)", \ + 9, cvtfr_dld9, cvtto_dld9), \ + vdk_fmt(VDK_FMT_DBW8, "DBW8", "Disk_BigEnd_Word (8)", \ + 2*8, cvtfr_dbw8, cvtto_dbw8), \ + vdk_fmt(VDK_FMT_DLW8, "DLW8", "Disk_LittleEnd_Word (8)", \ + 2*8, cvtfr_dlw8, cvtto_dlw8), \ + vdk_fmt(VDK_FMT_DBH4, "DBH4", "Disk_BigEnd_Halfword (8)", \ + 2*2*4, cvtfr_dbh4, cvtto_dbh4), \ + vdk_fmt(VDK_FMT_DLH4, "DLH4", "Disk_LittleEnd_Halfword (8)", \ + 2*2*4, cvtfr_dlh4, cvtto_dlh4) + +enum { +# define vdk_fmt(i,n,c,s,f,t) i + VDK_FORMATS + , VDK_FMT_N +# undef vdk_fmt +}; + + +#if VDK_DISKMAP +struct vdk_header { +# if 0 + char dh_id[4]; + int dh_ver; + + /* Other config params, etc etc */ +# endif + osdaddr_t dh_freep; +}; +#endif /* VDK_DISKMAP */ + +struct vdk_unit { + char dk_devname[16]; /* Device name, eg "RP06" */ + int dk_format; /* Data format */ + char *dk_filename; /* Pathname of diskfile */ + osfd_t dk_fd; /* Diskfile I/O handle */ + int dk_iswrite; /* TRUE if R/W, else RO */ + + /* Config info */ + int dk_dtype; /* Emulated disk type */ + unsigned dk_ncyls, /* Emulated # cylinders/unit */ + dk_ntrks, /* Emulated # tracks/cylinder */ + dk_nsecs; /* Emulated # sectors/track */ + unsigned dk_nwds; /* Emulated # words/sector */ + unsigned dk_bytesec; /* # real bytes per emulated sector */ + /* (depends on dk_format) */ + + unsigned char *dk_buf; /* M Pointer to conversion buffer */ + size_t dk_bufsiz; /* Size of conversion buffer */ + unsigned dk_bufsecs; /* # sectors that fit in buffer */ + void (*dk_fmt2wds)( /* Conversion routine, disk->mem */ + w10_t *, int, unsigned char *); + void (*dk_wds2fmt)( /* Conversion routine, mem->disk */ + unsigned char *, w10_t *, int); + char *dk_blkbuf; /* M Pointer to scratch block-size buffer */ + + void (*dk_errhan)( /* Error handler */ + struct vdk_unit *, char *); + char *dk_errarg; /* Arg to handler */ + int dk_err; /* # of last I/O error (0 if none) */ + +#if VDK_DISKMAP + int dk_ismap; /* TRUE if disk being mapped */ + struct vdk_header dk_dfh; /* Copy of diskfile header */ + int dk_blkalign; /* Block alignment at end of cylinder: */ + /* 0 - none, wrap uses next cyl */ + /* + - round up, wrap uses bogus sects */ + /* - - round down, wrap ignores sects */ + unsigned dk_secblk; /* Blocking factor: # sectors per block */ + unsigned dk_wdsblk; /* # words per block */ + uint32 dk_byteblk; /* # bytes per block */ + unsigned dk_blkcyl; /* # blocks per cylinder */ + uint32 dk_nblks; /* # blocks (# entries in map) */ + osdaddr_t *dk_map; /* M Pointer to map */ + osdaddr_t dk_seekp; /* Last requested seek location */ + osdaddr_t dk_freep; /* First known free location */ +#endif /* VDK_DISKMAP */ + +}; + + +/* Facility declarations */ + +extern int vdk_init(struct vdk_unit *, + void (*)(struct vdk_unit *, char *), char *); +extern int vdk_mount(struct vdk_unit *, char *, int); +extern int vdk_unmount(struct vdk_unit *); +extern int vdk_read(struct vdk_unit *, w10_t *, int32, int); +extern int vdk_write(struct vdk_unit *, w10_t *, int32, int); + +/* Compute block number given disk, cylinder, track, sector? */ +#define vdk_blknum(d,c,t,s) /* unfinished */ + + +#define vdk_ismounted(d) ((d)->dk_filename != NULL) +#define vdk_iswritable(d) ((d)->dk_iswrite) + +/* TRUE if track & sector is valid start of a block */ +#define vdk_secisblk(d,t,s) \ + (((((uint32)(t)*(d)->dk_nsecs) + (s)) % (d)->dk_secblk) ? FALSE : TRUE) + +#endif /* ifndef VDISK_INCLUDED */ diff --git a/src/vdkfmt.c b/src/vdkfmt.c new file mode 100644 index 0000000..6047242 --- /dev/null +++ b/src/vdkfmt.c @@ -0,0 +1,755 @@ +/* VDKFMT.C - Utility to copy or format virtual disks. +*/ +/* $Id: vdkfmt.c,v 2.5 2001/11/19 10:51:43 klh Exp $ +*/ +/* Copyright © 1994, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: vdkfmt.c,v $ + * Revision 2.5 2001/11/19 10:51:43 klh + * Bugfix: was freeing d_path which is now static. + * + * Revision 2.4 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* VDKFMT is mainly used to copy or format virtual disks. +** later it may become a general-purpose utility for managing +** KLH10 disk packs. +** +*/ + +#include +#include +#include +#include +#include /* exit() */ +#include +#include +#include /* For open() flags */ + +#include "rcsid.h" +#include "cenv.h" +#include "osdsup.h" +#include "vdisk.h" /* Include virtual disk stuff */ + +#if CENV_SYS_UNIX +# include /* Basic Unix syscalls */ +# include +# include +# include +# define NULLDEV "/dev/null" +# define FD_STDIN 0 +# define FD_STDOUT 1 +#endif + +#define FNAMSIZ 200 + +#define TRUE 1 +#define FALSE 0 + +#ifdef RCSID + RCSID(vdkfmt_c,"$Id: vdkfmt.c,v 2.5 2001/11/19 10:51:43 klh Exp $") +#endif + +/* Disk type configuration params. +** All of these numbers assume drives using 18-bit formatting. +** (16-bit formatting has more sectors per track) +*/ +static struct diskconf { + char dcf_name[8]; /* Drive type name (RP06, etc) */ + int dcf_type; /* Type bits for type register */ + int dcf_nwds; /* Words/Sector */ + int dcf_nsec; /* Sectors/Track */ + int dcf_ntrk; /* Tracks/Cylinder */ + int dcf_ncyl; /* Cylinders/Drive */ +} diskconfs[] = { + /* wrd sec trk cyl Cyl+maint totsec totmaintsec */ + { "RP02", 0, 128, 10, 20, 203 }, /* 200+3 40000 40,600 */ + { "RP03", 0, 128, 10, 20, 403 }, /* 400+3 80000 80,600 */ + + { "RP04", 0, 128, 20, 19, 411 }, /* 406+5 154280 156,180 */ + { "RP05", 0, 128, 20, 19, 411 }, /* " " " */ + { "RP06", 0, 128, 20, 19, 815 }, /* 810+5 307800 309,700 */ + { "RP07", 0, 128, 43, 32, 630 }, /* 629+1 865504 866,880 */ + { "RM03", 0, 128, 30, 5, 823 }, /* 821+2 123150 123,450 */ + { "RM02", 0, 128, 30, 5, 823 }, /* " " " */ + { "RM05", 0, 128, 30, 19, 823 }, /* 821+2 467970 469,110 */ + { "RM80", 0, 128, 30, 14, 561 }, /* 559+2 234780 235,620 */ + + { "" } +}; +/* RP07 doc p.6-23 says addressing params are: +** Mode Cyl Head/Track Sector(18) Sector(16) +** Functional: 630 32 43 50 +** Diagnostic: 632 32 43 50 +*/ + +/* In ITS: + RP06 was 812+3 + RP07 was 627+3 + RM03 was 821+2 + RM05 was 820 + RM80 was 556+3 +*/ + +/* VDKFMT parameters */ + +char nusage[] = "\ +Usage: vdkfmt \n\ + ip= Input disk device/file:\n\ + op= Output disk device\n\ + ifmt= format of input pack data\n\ + ofmt= format of output pack data\n\ + dt= Type of drive (RP06, etc)\n\ + log= Log filespec (optional, defaults to stderr)\n\ + verbose Verbose (optional)\n\ +"; + + + +/* Switch parameters */ +int sw_tdtest; +int sw_peot; +int sw_verbose; +int sw_maxsec; +int sw_maxfile; +char *sw_logpath; +FILE *logf; + +#define DBGFLG sw_verbose + +struct devdk { + char *d_pname; /* Print name, "In" or "Out" */ + char *d_path; /* Disk drive path spec */ + int d_isdisk; /* NZ if hardware device, else virtual */ + int d_fmt; /* Format to use */ + long d_totsec; /* Total # sectors */ + struct vdk_unit d_vdk; /* Virtual disk info */ + struct diskconf d_dcf; +}; +struct devdk dvi = { "In" }; +struct devdk dvo = { "Out" }; + +/* Values for d_isdisk */ +#define MTYP_NULL 0 /* Null device */ +#define MTYP_VIRT 1 /* Virtual disk */ +#define MTYP_HARD 2 /* Hard raw disk partition */ + +char *mtypstr[] = { + "nulldev", + "virtual", + "hard" +}; + +int cmdsget(int ac, char **av); +int docopy(void); +int zerosector(w10_t *wp, int nwds); + +int devopen(struct devdk *d, int wrtf); +int devclose(struct devdk *d); +int devread(struct devdk *d, long int daddr, w10_t *buff); +int devwrite(struct devdk *d, long int daddr, w10_t *buff); + +void swerror(char *fmt, ...); +void efatal(char *errmsg); +void errhan(struct vdk_unit *t, char *s); + +/* For now, must include VDISK source directly, so as to avoid compile-time +** switch conflicts (eg with KLH10). +*/ +#include "vdisk.c" + + +static int partyp(char *cp, struct diskconf *dcf) +{ + register struct diskconf *dc; + + for (dc = diskconfs; dc->dcf_name[0]; ++dc) { + if (strcasecmp(cp, dc->dcf_name) == 0) { + *dcf = *dc; /* Found match, won */ + return TRUE; + } + } + return FALSE; /* Unknown disk type */ +} + +/* Parse disk format -- something that VDISK understands. +*/ +static char *fmttab[] = { +# define vdk_fmt(i,n,c,s,f,t) n + VDK_FORMATS +# undef vdk_fmt +}; + +static int parfmt(char *cp, int *afmt) +{ + register int i; + + for (i = 0; i < VDK_FMT_N; ++i) { + if (strcasecmp(cp, fmttab[i]) == 0) { + *afmt = i; /* Found match, won */ + return TRUE; + } + } + return FALSE; /* Unknown disk format */ +} + +/* Error handling */ + +/* Copied from OSDSUP.C */ + +char * +os_strerror(int err) +{ + if (err == -1 && errno != err) + return os_strerror(errno); +#if CENV_SYSF_STRERROR + return strerror(err); +#else +# if CENV_SYS_UNIX + { +#if !CENV_SYS_XBSD + extern int sys_nerr; + extern char *sys_errlist[]; +#endif + if (0 < err && err <= sys_nerr) + return (char *)sys_errlist[err]; + } +# endif + if (err == 0) + return "No error"; + else { + static char ebuf[30]; + sprintf(ebuf, "Unknown-error-%d", err); + return ebuf; + } +#endif /* !CENV_SYSF_STRERROR */ +} + +void errhan(struct vdk_unit *t, char *s) +{ + fprintf(logf, "; %s: %s\n", t->dk_devname, s); +} + + +void efatal(char *errmsg) /* print error message and exit */ + /* error message string */ +{ + fflush(stdout); + fprintf(logf, "\n?%s\n",errmsg); + exit(1); +} + + +int +main(int argc, char **argv) +{ + int ret; + + logf = stderr; + signal(SIGINT, exit); /* Allow int to terminate log files etc */ + + dvi.d_fmt = dvo.d_fmt = -1; + + if (ret = cmdsget(argc, argv)) /* Parse and handle command line */ + exit(ret); + + + if (!sw_logpath) + logf = stderr; + else { + if ((logf = fopen(sw_logpath, "w")) == NULL) { + logf = stderr; + fprintf(logf, "; Cannot open log file \"%s\", using stderr.\n", + sw_logpath); + } + } + + /* Set up defaults for devices, and log all params if requested */ + dvi.d_totsec = dvo.d_totsec = + dvi.d_dcf.dcf_nsec * + dvi.d_dcf.dcf_ntrk * + dvi.d_dcf.dcf_ncyl; + if (sw_verbose) { + fprintf(logf, "; Input disk spec \"%s\" (Type: %s) Format: %s\n", + dvi.d_path, mtypstr[dvi.d_isdisk], + fmttab[dvi.d_fmt]); + fprintf(logf, "; Output disk spec \"%s\" (Type: %s) Format: %s\n", + dvo.d_path, mtypstr[dvo.d_isdisk], + fmttab[dvo.d_fmt]); + + /* Show config info here */ + fprintf(logf, "; Drive type: %s\n", dvi.d_dcf.dcf_name); + fprintf(logf, "; %ld sectors, %d wds/sec\n", dvi.d_totsec, + dvi.d_dcf.dcf_nwds); + fprintf(logf, "; (%d secs, %d trks, %d cyls\n", + dvi.d_dcf.dcf_nsec, + dvi.d_dcf.dcf_ntrk, + dvi.d_dcf.dcf_ncyl); + if (sw_logpath) + fprintf(logf, "; Using logging path %s\n", sw_logpath); + } + + /* Open I/O files as appropriate */ + if (!devopen(&dvi, FALSE)) /* Open for reading */ + exit(1); + if (!devopen(&dvo, TRUE)) /* Open for writing */ + exit(1); + + /* Do it! */ + fprintf(logf, "; Copying from \"%s\" to \"%s\"...\n", dvi.d_path, + dvo.d_path ? dvo.d_path : NULLDEV); + if (ret = docopy()) + ret = devclose(&dvo); + else (void) devclose(&dvo); + + if (!ret) fprintf(logf, "; Stopped unexpectedly.\n"); + +#if 0 + { + register struct devdk *d; + for (d = &dvi; d; d = (d == &dvi) ? &dvo : NULL) { + if (d->d_isdisk) + fprintf(logf, "; %s: %d+%d errs, %d secs, %ld bytes\n", + d->d_pname, d->mta_herr, d->mta_serr, + d->d_secs, d->d_tloc); + } + } +#endif + + fclose(logf); + exit(ret ? 0 : 1); +} + +static char pagsym[4] = { '.', '-', '=', '#'}; + +int docopy(void) +{ + int err; + long nsect = 0; + w10_t wbuff[512]; + int nwrt = 0; + + if (DBGFLG) + fprintf(logf, "; Pages:\n"); + + for (; nsect <= dvi.d_totsec;) { + + err = devread(&dvi, nsect, wbuff); /* Get a sector */ + if (!err) { + fprintf(logf, "; Aborting loop, last err: %s\n", os_strerror(-1)); + return 0; + } + + /* See whether there's any data in sector or not. + ** If none, don't write it out! + ** Later, always write if device is "hard". + */ + if (!zerosector(wbuff, 128)) { + /* Copy results to output device */ + nwrt++; + err = devwrite(&dvo, nsect, wbuff); /* Write a sector */ + if (!err) { + fprintf(logf, "; Aborting loop, last err: %s\n", + os_strerror(-1)); + return 0; + } + } + + ++nsect; + + /* Hack to show nice pattern, one char per 4-sector page */ + if (DBGFLG) { + if ((nsect & 03) == 0) { + putc(pagsym[nwrt&03], logf); + nwrt = 0; + } + } + } + + if (DBGFLG) + fprintf(logf, "\n"); + return TRUE; +} + +int zerosector(register w10_t *wp, register int nwds) +{ + for (; --nwds >= 0; ++wp) + if (LHGET(*wp) || RHGET(*wp)) + return FALSE; + return TRUE; +} + + +int swerrs = 0; + +void swerror(char *fmt, ...) +{ + ++swerrs; + { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + putc('\n', stderr); +} + +int +cmdsget(int ac, char **av) +{ + register char *cp, *arg; + register int plen; + struct devdk *d; + + dvi.d_isdisk = dvo.d_isdisk = MTYP_NULL; + + while (--ac > 0 && (cp = *++av)) { + if (arg = strchr(cp, '=')) /* If arg furnished for param, */ + *arg++ = '\0'; /* split arg off */ + if ((plen = strlen(cp)) <= 0) + break; /* Bad param */ + + /* Now identify parameter. No case folding for now, sigh */ + if (*cp == 'i' || *cp == 'o') { + /* Handle {i,o}{t,d,f,b} */ + if (!arg) { + swerror("Parameter requires arg: \"%s\"", *av); + continue; + } + d = (*cp == 'i') ? &dvi : &dvo; + switch (*++cp) { + case 'p': + if (strlen(cp) > 2) break; + if (d->d_path) { + swerror("Param already specified: \"%s\"", *av); + continue; + } + d->d_path = arg; + switch (*++cp) { + case '\0': + case 'v': d->d_isdisk = MTYP_VIRT; continue; + case 'h': d->d_isdisk = MTYP_HARD; continue; + /* default falls thru to fail */ + } + break; + case 'f': + if (strcmp(cp, "fmt")!=0) + break; + if (d->d_fmt != -1) { + swerror("Param already specified: \"%s\"", *av); + continue; + } + if (!parfmt(arg, &(d->d_fmt))) { + swerror("Unknown format: \"%s\"", arg); + continue; + } + continue; + + /* Default just drops thru to fail */ + } + swerror("Unknown parameter \"%s\"", *av); + continue; + } + + if (strcmp(cp, "drive")==0 || strcmp(cp, "dt")==0) { + if (!partyp(arg, &(dvi.d_dcf))) + swerror("Unknown drive type: \"%s\"", arg ? arg : ""); + continue; + + } else if (strcmp(cp, "log")==0) { + if (!(sw_logpath = arg)) + swerror("Bad arg to log: \"\""); + continue; + + } else if (strcmp(cp, "verbose")==0 || strcmp(cp, "v")) { + sw_verbose = TRUE; + continue; + } + + swerror("Unknown parameter \"%s\"\n%s", *av, nusage); + return 1; + } + + /* Clean up params */ + if (dvi.d_dcf.dcf_nwds) + dvo.d_dcf = dvi.d_dcf; /* Copy drive type params */ + else + swerror("Drive type must be specified"); + if (dvi.d_fmt == -1) + swerror("Input pack format must be specified"); + if (dvo.d_fmt == -1) + swerror("Output pack format must be specified"); + + /* Check for any parameter errors */ + if (swerrs) { + fprintf(stderr, "%s", nusage); + return swerrs; + } + + return 0; +} + +/* Generic device routines (null, virtual, and real) +** Open, Close, Read, Write, Write-EOF, Write-EOT. +*/ + +int devopen(register struct devdk *d, int wrtf) +{ + char *opath, *path; + + if (!(opath = d->d_path) || !*opath) { + fprintf(logf, "; Null mount path\n"); + return FALSE; + } + path = malloc(strlen(opath)+1); + strcpy(path, opath); + + /* Set up config vars */ + vdk_init(&(d->d_vdk), errhan, NULL); + + d->d_vdk.dk_format = d->d_fmt; + strcpy(d->d_vdk.dk_devname, d->d_dcf.dcf_name); + d->d_vdk.dk_ncyls = d->d_dcf.dcf_ncyl; + d->d_vdk.dk_ntrks = d->d_dcf.dcf_ntrk; + d->d_vdk.dk_nsecs = d->d_dcf.dcf_nsec; + d->d_vdk.dk_nwds = d->d_dcf.dcf_nwds; + + if (!vdk_mount(&(d->d_vdk), path, wrtf)) { + fprintf(logf, "; Cannot mount device \"%s\": %s\n", + path, os_strerror(d->d_vdk.dk_err)); + free(path); + return FALSE; + } + free(path); + + return TRUE; +} + +int devclose(struct devdk *d) +{ + int res = TRUE; + + if (DBGFLG && d->d_path) + fprintf(logf, "; Closing \"%s\"\n", d->d_path); + + if (d->d_isdisk) { + res = vdk_unmount(&(d->d_vdk)); /* Close real disk */ + } + + /* Force us to forget about it even if above stuff failed */ + d->d_isdisk = FALSE; + return res; +} + + +/* Read from device +** Returns 1 if read something +** Returns 0 if read nothing or error +*/ + +int devread(struct devdk *d, long int daddr, w10_t *buff) +{ + int nsec; + +#if 0 + if (DBGFLG) + fprintf(logf, "; read daddr=%ld\n", daddr); +#endif + + nsec = vdk_read(&d->d_vdk, buff, (int32)daddr, 1); + + if (d->d_vdk.dk_err + || (nsec != 1)) { + fprintf(logf, "; read error on %s: %s\n", + d->d_vdk.dk_filename, os_strerror(d->d_vdk.dk_err)); + return FALSE; + } + return TRUE; +} + +/* Write to device. +*/ +int devwrite(struct devdk *d, long int daddr, w10_t *buff) +{ + int nsec; + +#if 0 + if (DBGFLG) + fprintf(logf, "; write daddr=%ld\n", daddr); +#endif + + nsec = vdk_write(&d->d_vdk, buff, (int32)daddr, 1); + + if (d->d_vdk.dk_err + || (nsec != 1)) { + fprintf(logf, "; write error on %s: %s\n", + d->d_vdk.dk_filename, os_strerror(d->d_vdk.dk_err)); + return FALSE; + } + + return TRUE; +} + + + +/* General-purpose System-level I/O. +** It is intended that this level of IO be in some sense the fastest +** or most efficient way to interact with the host OS, as opposed to +** the more portable stdio interface. +*/ + +int +os_fdopen(osfd_t *afd, char *file, char *modes) +{ +#if CENV_SYS_UNIX || CENV_SYS_MAC + int flags = 0; + if (!afd) return FALSE; + for (; modes && *modes; ++modes) switch (*modes) { + case 'r': flags |= O_RDONLY; break; /* Yes I know it's 0 */ + case 'w': flags |= O_WRONLY; break; + case '+': flags |= O_RDWR; break; + case 'a': flags |= O_APPEND; break; + case 'b': /* Binary */ +# if CENV_SYS_MAC + flags |= O_BINARY; +# endif + break; + case 'c': flags |= O_CREAT; break; + /* Ignore unknown chars for now */ + } +# if CENV_SYS_MAC + if ((*afd = open(file, flags)) < 0) +# else + if ((*afd = open(file, flags, 0666)) < 0) +# endif + return FALSE; + return TRUE; + +#elif CENV_SYS_MOONMAC + Boolean create = FALSE, append = FALSE; + short refnum; + OSErr err; + char pascal_string[256]; + + if (!afd) return FALSE; + for (; modes && *modes; ++modes) switch (*modes) { + case 'a': append = TRUE; break; + case 'c': create = TRUE; break; + /* Ignore read/write and unknown chars for now */ + } + pascal_string[0] = strlen(file); + BlockMove(file, &pascal_string[1], pascal_string[0]); + if (create) Create(pascal_string, 0, 'KH10', 'TEXT'); + err = FSOpen(pascal_string, 0, &refnum); + *afd = err; + if (err) return FALSE; + *afd = refnum; + return TRUE; +#endif +} + +int +os_fdclose(osfd_t fd) +{ +#if CENV_SYS_UNIX || CENV_SYS_MAC + return close(fd) != -1; +#else + return 0; +#endif +} + +int +os_fdseek(osfd_t fd, osdaddr_t addr) +{ +#if CENV_SYS_UNIX + return lseek(fd, addr, L_SET) != -1; +#elif CENV_SYS_MAC || CENV_SYS_MOONMAC + return lseek(fd, addr, SEEK_SET) != -1; +#else + return 0; +#endif +} + +int +os_fdread(osfd_t fd, char *buf, size_t len, size_t *ares) +{ +#if CENV_SYS_UNIX || CENV_SYS_MOONMAC + register int res = read(fd, buf, len); + if (res < 0) { + if (ares) *ares = 0; + return FALSE; + } +#elif CENV_SYS_MAC + /* This is actually generic code for any system supporting unix-like + ** calls with a 16-bit integer count interface. + */ + register size_t res = 0; + register unsigned int scnt, sres = 0; + + while (len) { + scnt = len > (1<<14) ? (1<<14) : len; /* 16-bit count each whack */ + if ((sres = read(fd, buf, cnt)) != scnt) { + if (sres == -1) { /* If didn't complete, check for err */ + if (ares) *ares = res; /* Error, but may have read stuff */ + return FALSE; + } + res += sres; /* No error, just update count */ + break; /* and return successfully */ + } + res += sres; + len -= sres; + buf += sres; + } +#endif + if (ares) *ares = res; + return TRUE; +} + +int +os_fdwrite(osfd_t fd, char *buf, size_t len, size_t *ares) +{ +#if CENV_SYS_UNIX || CENV_SYS_MOONMAC + register int res = write(fd, buf, len); + if (res < 0) { + if (ares) *ares = 0; + return FALSE; + } +#elif CENV_SYS_MAC + /* This is actually generic code for any system supporting unix-like + ** calls with a 16-bit integer count interface. + */ + register size_t res = 0; + register unsigned int scnt, sres = 0; + + while (len) { + scnt = len > (1<<14) ? (1<<14) : len; /* 16-bit count each whack */ + if ((sres = write(fd, buf, cnt)) != scnt) { + if (sres == -1) { /* If didn't complete, check for err */ + if (ares) *ares = res; /* Error, but may have written stuff */ + return FALSE; + } + res += sres; /* No error, just update count */ + break; /* and return successfully */ + } + res += sres; + len -= sres; + buf += sres; + } +#endif + if (ares) *ares = res; + return TRUE; +} diff --git a/src/vmtape.c b/src/vmtape.c new file mode 100644 index 0000000..8309cca --- /dev/null +++ b/src/vmtape.c @@ -0,0 +1,3931 @@ +/* VMTAPE.C - Virtual Magnetic-Tape support routines +*/ +/* $Id: vmtape.c,v 2.5 2001/11/19 10:41:28 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: vmtape.c,v $ + * Revision 2.5 2001/11/19 10:41:28 klh + * Fix TPS format WEOF. + * + * Revision 2.4 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* + + The purpose of VMTAPE is primarily to invent the contents of a +virtual magtape on the fly, as it is being read, by following the +directions of the currently mounted tape-file. This tape-file may +variously be referred to as a "data file", "control file" or even +"tape directory" since it may specify many different files which are +to be sucked up to serve as the contents of the magtape. + +vmt_attrmount() is called when the user wants to "mount" a magtape, either +for reading or writing. For reading, the specified tape-file is read +in and parsed into an internal list that will be referenced when data +is requested from the drive. Any errors during the tape-file parse +will cause vmt_attrmount() to fail. Errors during the reading of +subsidiary files simply skip over those files. Both generate error +messages to the stdio stream provided. + +For writing, no attempt is made to understand the contents of the data +being written, and only raw-format or TPS/TPE output is supported. A data +file is generated, called .tap or .tps, and the +control file itself (if one is required) is not output until the tape +is rewound or unmounted. + +See vmtape.h for more on formats. + +*/ + +/* To-do Notes and stuff: + + - Long standing annoyance: Overhaul all refs to use a +consistent terminology such as "tape control file" as opposed to "tape +directory file" to avoid confusing overload of "d" in names. (data, +directory, description, ...) Then "tapefile" refers to either an OS +filespec for hardware, or a pair of control/data disk files, or a +single file with embedded structure? + + - Tied in with above, establish conventions for naming the +various formats, especially filename extensions. Main problem is that +everyone likes to use .TAP! One idea is to require that all tape +files use an extension of .TPx, such as .tpc, .tps, .tpr. + + - A personal desiderata is to finally settle on some extension +for the control/tapedir file that sorted AHEAD of its raw data file; +.ctl is good, but is not a .TPx. .TPC is "taken". .TPD? That letter +"D" again. Sigh! + + - Add "oneway" flag to better support unzip pipes or TPC-like +formats. Could apply to any data format. + + - Implement "inmem" feature, initially only for RO tapes. +What internal format to use? Convert to use TDR, or TPS? In order +to accomodate R/W would have to either extend TDR a bit or snarf in +the SBSTR package (latter probably gross overkill). + + - Allow using TDR information with formats other than RAW. This +would allow TPC to work better, as well as INMEM. + + - Flush REOF even for raw mode? Or extend it to one-way +formats so they can backspace over one tapemark at least. + + - Provide vmt_attrget()? + + - Someday move all OS_ stuff of dptm03/tapedd into a shared +osdtap.c. Then can also consider optionally extending vmtape.c to +hardware devices so as to finally provide a consistent interface. + +*/ + + +#include +#include +#include /* For malloc */ +#include +#include +#include +#include /* For error-reporting functions */ + +#include "klh10.h" +#include "osdsup.h" +#include "word10.h" +#include "wfio.h" /* For word-based file i/o */ +#include "vmtape.h" + +#ifdef RCSID + RCSID(vmtape_c,"$Id: vmtape.c,v 2.5 2001/11/19 10:41:28 klh Exp $") +#endif + +/* External OS-dependent definitions needed, acquired from file that + included us. +*/ +#if 0 +/* OS_MAXPATHLEN */ +extern char *os_strerror(int); +#endif + +/* Internal OSD stuff we currently include (later move into osdtap.c?) + */ +#if VMTAPE_ITSDUMP +static int os_fullpath(char *loc, char *dir, char *file, int maxlen); +static int os_fmtime(FILE *f, register struct tm *tm); +static int os_net2tm(long unsigned int ntm, register struct tm *tm); +#endif /* VMTAPE_ITSDUMP */ + + +/* Data & misc defs */ +struct vmtfmt_s { + int tf_fmt; + char *tf_name; + char *tf_ext; + int tf_flags; +}; +static struct vmtfmt_s vmtfmttab[] = { +#define vmtfdef(en,str,ext,flgs) { en, str, ext, flgs } + VMTFORMATS +#undef vmtfdef +}; + +/* Predeclarations */ +static int datf_open(struct vmtape *t, char *fname, char *mode); +static void datf_close(struct vmtape *t); +static int tdr_scan(struct vmtape *t, FILE *f, char *fname); +static char *dupcstr(char *s); +static void tdr_reset(struct vmtape *t); +static void td_init(struct vmttdrdef *td); +static void td_reset(struct vmttdrdef *td); +static void td_fout(struct vmttdrdef *td, FILE *f, char *fnam); +static void td_trunc(struct vmttdrdef *td); +static struct vmtrecdef *td_recapp(struct vmttdrdef *td, + long unsigned int len, int cnt, int err); + +#if 0 +# define vmtseterr(t) (((t)->mt_state = TS_ERR), (t)->mt_err++) +#else +# define vmtseterr(t) ((t)->mt_err++) +#endif + +#if VMTAPE_ITSDUMP + +/* Definitions for ITSDUMP format + */ + +/* Format of ITS DUMP tapes, from SYSENG;DUMP > + +THBLK: -LTHBLK,,0 ;TAPE-HEADER-BLOCK +THTPN: -1 ;TAPE NUMBER,,NUMBER OF REEL IN THIS DUMP, -1==> WE DON'T YET KNOW THIS PARAMETER +THDATE: 0 ;TAPE CREATION DATE (SIXBIT) +THTYPE: 0 ;0 RANDOM, >0 FULL DUMP, <0 INCREMENTAL DUMP +LTHBLK==.-THBLK +LTHTPN: -1 ;LAST THTPN + +HBLK: -LHBLK,,0 ;FILE-HEADER-BLOCK +HSNM: 0 ;SYS NAME +HFN1: 0 ;FN1 +HFN2: 0 ;FN2 +LHBLKZ==.-HBLK ; Must be at least this long +HPKN: 0 ;LINK FLAG,,PACK NUMBER (SAME INFO AS 3RD VALUE FROM + ; FILBLK, BUT IN A DIFFERENT FORMAT) +HDATE: 0 ;CREATION DATE AND TIME OF FILE ON DISK (DISK-FORMAT) (4TH + ; VALUE FROM FILBLK) + ; Next two added 7/14/89 by Alan +HRDATE: 0 ;REFERENCE DATE, AUTHOR INDEX, BYTE SIZE AND BYTE COUNT + ; (5TH VALUE FROM FILBLK) +HLEN: 0 ;LENGTH OF FILE IN WORDS (FROM FILLEN) +LHBLK==.-HBLK ; Must not be longer than this + +Note that if the file is actually a link, its length is assumed to be 3 +words, and the contents are (in order) the FN1, FN2, and DIR of the file +pointed to. + +A tapemark separates each file from the next. The exact record length +does not matter, except that the last record of each file is only as +long as needed to include the last words of the file. Conceivably +each file could be a single long record of varying size, but in +practice ITS DUMP seems to use a standard buffer size of 1024 words +(5120 frames in core-dump format) for all records except the last one. + +Double tapemark is logical EOT, as usual. + +Standard data mode for ITSDUMP is core-dump; nothing else makes sense. + +*/ + +#define VMT_ITS_FPW (5) /* Frames per word */ +#define VMT_ITS_DEFRECLEN (1024*VMT_ITS_FPW) /* Default record length */ + +enum { TH_CNT=0, /* File-block header, -4,,0 to -8,,0 */ + TH_DIR, /* Dir */ + TH_FN1, /* FN1 */ + TH_FN2, /* FN2 */ + TH_LKF, /* ,, */ + TH_CRD, /* */ + TH_RFD, /* ,, */ + /* -1,,777000 if unknown */ + TH_FLN, /* */ + /* -1 if unknown */ + TH_LFN1, /* Link target FN1, FN2, DIR */ + TH_LFN2, + TH_LDIR +}; + +enum efdfmt { FDFMT_WFT=1, FDFMT_LINK }; + + +struct vmtfdits { + int linkf; + w10_t dir, fn1, fn2; + w10_t xdir, xfn1, xfn2; + w10_t crdatim; + w10_t rfdatetc; + w10_t wdlen; + w10_t auth; /* Not used as yet, but maybe someday... */ +}; + + +struct vmtfildef { /* Always malloced */ + struct vmtfildef *fdprev; + struct vmtfildef *fdnext; + char *fdfname; /* Source filename on host system */ + + enum efdfmt fdfmt; + enum wftypes fdwft; + struct vmtfdits fdits; /* ITS filespec */ +}; + +static w10_t itsdumphead[4] = { /* Tape header */ + W10XWD(0777774, 0), /* <-# wds (-4)>,,0 */ + W10XWD(1, 0), /* ,, */ + W10XWD(0312220, 0262125), /* YYMMDD in sixbit */ + W10XWD(0, 0) /* 0 = random */ +}; + +static w10_t sixbit(char *str); +static w10_t itsdate(FILE *f); +static w10_t itsnet2qdat(uint32 ntm); +static w10_t itsfillen(uint32 bsz, uint32 bcnt, w10_t *rfd); + +static int its_wget(struct vmtape *t, w10_t *wp); + + +#endif /* VMTAPE_ITSDUMP */ + +/* Definitions for RAW format + */ + +#if VMTAPE_RAW + +/* +** A tape is considered to consist of an arbitrary sequence of data +** records and tapemarks. The logical position whenever idle (not in the +** middle of an IO transfer) is always at a point in between two such +** records or tapemarks (or BOT or EOT). +** +** For RAW format, the raw tape data is exactly that -- a file of +** nothing but data bytes with no internal structuring to indicate record +** lengths or tapemarks. Structure is imposed on this data with a "tape +** directory" that internally consists of a linked list of "vmtrecdef" +** structures, and externally is represented by an ASCII "control" or +** "tapedir" file. +** The reasons for using this technique rather than something +** with internal structure (eg TPS) are largely historical -- in order +** to save tapes with the hardware I could access, they had to be copied +** directly onto media such as QIC tapes which had no concept of record +** length, and it wasn't clear whether anything better would come along. +** +** RAW tape state info: +** +** When idle: +** tdcur may be: +** NULL -- tape is at BOT or EOT depending on whether tdrncur is 0 or 1. +** Else points to LAST record def transferred. +** tdrncur likewise indicates last record # transferred; this +** will be a number from 1 to N. If 0, none of the records +** in the current definition have been transferred yet. +** If N or N+1, all of the current record definition has been +** transferred. +** Note that a tapemark (EOF) is considered a record of itself, with +** a length & count of 0. If tdcur points at a tapemark, tdrncur +** will be 0 to indicate a logical position before the tapemark, +** else >= 1 to indicate a position after it. +** +** The function vmt_iobeg() is used to initialize the tape state from +** the above intermediate state to one of the states below: +** +** During read IO transfer (state TS_RDATA): +** tdcur -> the current record definition structure, +** tdrncur = (nonzero) indicates which of the possibly N records +** defined is actually about to be read by the next IO transfer. +** During read IO transfer (state TS_REOF): +** tdcur -> the current tapemark record +** tdrncur = 0 +** During read IO transfer (state TS_EOT): +** tdcur = NULL +** tdrncur = 1 +** +** During write IO transfer (state TS_WRITE): +** tdcur = NULL +** tdrncur = 1 to indicate tape is at EOT. +** The tape directory info has been munged to forget about all +** previous info after the current write point. +** When the IO transfer completes, the record or tapemark written +** will be appended to the end of the tape directory. +*/ + +struct vmtrecdef { /* Always malloced */ + struct vmtrecdef *rdnext; /* Next record */ + struct vmtrecdef *rdprev; /* Prev record */ + unsigned long rdloc; /* Record loc */ + unsigned long rdlen; /* Record length (can be 0 if tapemark/err) */ + unsigned int rdcnt; /* # of recs of this length (0 iff tapemark) */ + int rderr; /* If non-zero, error reading this record */ +}; + +#endif /* VMTAPE_RAW */ + +/* Definitions for Bob Supnik's SIMH magtape format + (also attributed to John Wilson's E11 tape format, although E11 + format differs in NOT having a padding byte!) + +"TPS" FILE FORMAT (.TAP, .TPS, .TPE) +==================================== + + This is the format used by Bob Supnik's SIMH emulators. It is +almost the same as John Wilson's E11 format, which it was intended to +resemble. + +A TPS file is assumed to consist of 8-bit bytes. + +Each record starts with a 4-byte header in little-endian order. +The high bit of this 32-bit header is set to indicate an error of +some kind (similar to the 'E' flag in a RAW-format control file) and +the remaining 31 bits contain the record length N. + +This header is followed by N data bytes plus, if N is odd, a padding +byte (of undefined value) at the end. Following this is a copy of the +4-byte record header. + +Tapemarks are represented by a single header with N=0. + +The reason for having the record length both before and after the +record data is so tape motion in the reverse direction can be more +easily simulated. + +The E11 (.TPE) format is identical except there are no padding bytes. + + */ + +/* No need to maintain record structure information as it can be + determined by reading from the data file. + Perhaps later maintain I/O pointer in case it eliminates some seeks, + but because of stdio buffering this doesn't matter a great deal. + + Tape file will be opened on t->mt_datf as a binary stream. + SEEK position will always point to the start of the next record + or tapemark (assuming going forward). + + Thus, to read a record/mark: read the header to find the length, + read the data if present, and read the trailer header to confirm it + matches, thus leaving the ptr at the start of the next record. + + To write: write out the header, then the data, then the trailer. + + Must do so portably, in little-endian order. + */ + +#define VMT_TPS_ERRF (1UL<<31) /* Error - high order bit of record header */ +#define VMT_TPS_CNTF (VMT_TPS_ERRF-1) +#define VMT_TPS_COUNT(ucp) (\ + (((uint32)(ucp)[3])<<24) | \ + (((uint32)(ucp)[2])<<16) | \ + ((ucp)[1] << 8) | \ + ((ucp)[0])) + +/* + +"TPC" FILE FORMAT (.TPC) +======================== + + This format is not as flexible as the others but was +apparently quite commonly used for DECUS tape images, and Tim Shoppa +has a "couple thousand" TPC images stashed away. + +A TPC file is assumed to consist of 8-bit bytes. + +Each record starts with a 2-byte header in little-endian order, +containing 16 bits of record length N. + +This header is followed by N data bytes plus, if N is odd, a padding +byte (of undefined value) at the end. Unlike TPS format, there is no +trailing header. + +Tapemarks are represented by a single header with N=0. + +Obviously it is difficult to use this format directly when reverse +tape motion is desired; an internal representation must be built. + + */ +#define VMT_TPC_COUNT(ucp) (((ucp)[1]<<8)|((ucp)[0])) + + + +void +vmt_init(register struct vmtape *t, + char *devnam) +{ +#if 1 + memset((char *)t, 0, sizeof(*t)); /* For now */ +#else + td_init(&t->mt_tdr); + t->mt_state = TS_CLOSED; +#endif + t->mt_devname = devnam; + t->mt_errhan = NULL; +} + + +/* Error reporting for all VMTAPE code. +** Currently uses a fixed-size error string buffer. This is dumb but +** simple; all calling routines must make some effort to keep length +** within bounds. +** Specifically, never use plain "%s" -- always limit it. +*/ +static void +vmterror(register struct vmtape *t, char *fmt, ...) +{ + char ebuf[512]; + + if (t->mt_devname == NULL) + t->mt_devname = "<\?\?\?>"; + { + va_list ap; + va_start(ap, fmt); + vsprintf(ebuf, fmt, ap); + va_end(ap); + } + + if (t->mt_errhan == NULL) + fprintf(stderr, "[%s: %s]\n", t->mt_devname, ebuf); + else + (*(t->mt_errhan))(t->mt_errarg, t, ebuf); +} + +/* Useful variant for reporting tape-dir parsing errors +*/ +static void +tdrwarn(register struct vmtape *t, char *fmt, ...) +{ + char ebuf[512]; + + if (t->mt_devname == NULL) + t->mt_devname = "<\?\?\?>"; + sprintf(ebuf, "Tapedir error, file \"%.128s\" line %d: ", + t->mt_filename ? t->mt_filename : "", t->mt_lineno); + { + va_list ap; + va_start(ap, fmt); + vsprintf(ebuf+strlen(ebuf), fmt, ap); + va_end(ap); + } + + if (t->mt_errhan == NULL) + fprintf(stderr, "[%s: %s]\n", t->mt_devname, ebuf); + else + (*(t->mt_errhan))(t->mt_errarg, t, ebuf); +} + + +static int vmt_crmount(struct vmtape *t, struct vmtattrs *ta); +static int vmt_rdmount(struct vmtape *t, struct vmtattrs *ta); +static int fmtexamine(struct vmtape *t, FILE *f, int fmt); +static char *os_pathext(char *path); +static char *genpath(struct vmtape *t, char *path, char *oldext, char *newext); + +int +vmt_unmount(register struct vmtape *t) +{ + int res = TRUE; + + /* vmt_unmount must be callable from any state, even intermediate ones + ** where tape was incompletely opened. So always check everything. + */ + if (t->mt_datf) { /* Have an open data file? */ + /* If still any output to deliver, ensure it's out by doing rewind. */ + res = vmt_rewind(t); /* Remember if output finalization fails */ + } + + datf_close(t); /* Close either input or output data */ + if (t->mt_ctlf) { + fclose(t->mt_ctlf); + t->mt_ctlf = NULL; + } + tdr_reset(t); /* Flush TDR and FDF stuff */ + + if (t->mt_datpath) { + free(t->mt_datpath); + t->mt_datpath = NULL; + } + if (t->mt_filename) { + free(t->mt_filename); + t->mt_filename = NULL; + } + + t->mt_state = TS_CLOSED; /* Must be 0 */ + t->mt_writable = FALSE; + t->mt_format = VMT_FMT_UNK; + t->mt_fmtp = &vmtfmttab[t->mt_format]; + t->mt_frames = t->mt_err = 0; + t->mt_iowrt = t->mt_eof = t->mt_eot = t->mt_bot = FALSE; + return res; +} + +int +vmt_pathmount(struct vmtape *t, char *path, char *args) +{ + struct vmtattrs v; + + if (!path) /* If just wanted unmount, that's all! */ + return vmt_unmount(t); + + /* Set up initial path */ + if (strlen(path) >= sizeof(v.vmta_path)) { + vmterror(t, "mount path too long (%d max)", (int)sizeof(v.vmta_path)); + return FALSE; + } + strcpy(v.vmta_path, path); /* Set path */ + v.vmta_mask = VMTA_PATH; /* Initialize attribs */ + + /* Parse args if any, adding to attribs */ + if (args && !vmt_attrparse(t, &v, args)) { + /* Already reported any errors */ + return FALSE; + } + return vmt_attrmount(t, &v); +} + +int +vmt_attrmount(struct vmtape *t, struct vmtattrs *ta) +{ + char *pext; + enum vmtfmt efmt = 0, fmt; + char *efn = NULL; + + vmt_unmount(t); /* Always unmount old tape if any */ + + if (!(ta->vmta_mask & VMTA_PATH) || !ta->vmta_path[0]) { + vmterror(t, "no pathname given to mount"); + return FALSE; + } + + /* Set defaults for unspecified stuff */ + if (!(ta->vmta_mask & VMTA_CTLPATH)) + ta->vmta_ctlpath[0] = '\0'; + if (!(ta->vmta_mask & VMTA_DATPATH)) + ta->vmta_datpath[0] = '\0'; + if (!(ta->vmta_mask & VMTA_MODE)) + ta->vmta_mode = VMT_MODE_RDONLY; /* Default mode is RO */ + if (!(ta->vmta_mask & VMTA_UNZIP)) + ta->vmta_unzip = FALSE; + + /* See if extension implies a format. May not need this, but + to simplify coding always do it upfront. + */ + pext = os_pathext(ta->vmta_path); /* Find file ext if any */ + if (pext) { + efmt = vmt_exttofmt(pext); + } + + if (ta->vmta_mask & VMTA_FMTREQ) + fmt = ta->vmta_fmtreq; /* Explicit request */ + else if (efmt) + fmt = efmt; /* Filename extension spec */ + else if (ta->vmta_mask & VMTA_FMTDFLT) + fmt = ta->vmta_fmtdflt; + else + fmt = 0; + + if (ta->vmta_mode & VMT_MODE_CREATE) { + /* Create new tapefile, must not exist. + Format is determined first by explicit request, then by + filename extension, then by user default, then coded default. + CTL is turned into coded default (RAW). + Only RAW and TPS/TPE are currently supported for output. + */ + ta->vmta_fmtreq = fmt ? fmt : VMTAPE_FMT_DEFAULT; + return vmt_crmount(t, ta); + + } else if (ta->vmta_mode & VMT_MODE_UPDATE) { + vmterror(t, "Update of existing tapefile not yet supported"); + return FALSE; + + } else if (ta->vmta_mode & VMT_MODE_RDONLY) { + /* Tapefile must exist; determine format. + Determined first by explicit request (& verified) + then filename ext (& verified), then by user default + (& verified), finally by examination. + If cannot determine, give up and complain. + */ + ta->vmta_fmtreq = fmt ? fmt : VMT_FMT_UNK; + + return vmt_rdmount(t, ta); + } +} + +/* VMT_CRMOUNT - Create and mount a new virtual tape for +** writing. +*/ +static int +vmt_crmount(register struct vmtape *t, + register struct vmtattrs *ta) +{ + FILE *cf = NULL, *df; + char *cfn = NULL, *dfn = NULL; + + enum vmtfmt fmt = ta->vmta_fmtreq; + struct vmtfmt_s *fmtp = &vmtfmttab[fmt]; + char *pext; + + /* Check format. This weeds out CTL, for example. + * Currently only supports RAW, TPE, TPS. + */ + if (!(fmtp->tf_flags & VMTFF_WR)) { + vmterror(t, "Unwritable tape format %s", + fmtp->tf_name); + return FALSE; + } + pext = os_pathext(ta->vmta_path); /* Will probably want this */ + + /* First verify necessary filenames are present */ + if (fmtp->tf_flags & (VMTFF_CTL | VMTFF_XCTL)) { + /* If explicit ctlpath given, use it without question */ + if ((cfn = ta->vmta_ctlpath) && *cfn) + cfn = dupcstr(cfn); + else if ((cfn = ta->vmta_path) && *cfn) { + /* Normal path, Add CTL extension if none present */ + cfn = pext ? dupcstr(cfn) + : genpath(t, cfn, NULL, vmtfmttab[VMT_FMT_CTL].tf_ext); + } else { + vmterror(t, "No tape control filename"); + return FALSE; + } + if (!cfn) { + vmterror(t, "No mem for tape control filename"); + return FALSE; + } + } + /* From here on must fail by going to badret, since cfn must be freed */ + + /* Ditto for output data filename, which must always be present */ + if ((dfn = ta->vmta_datpath) && *dfn) + dfn = dupcstr(dfn); + else if ((dfn = ta->vmta_path) && *dfn) { + /* Use normal path. A bit tricky to ensure right extension. + If no extension given, or path was for the CTL file, + ensure it has a new extension identifying the format. + Otherwise (has ext and fmt doesn't use CTL file), use the + given path as-is. + */ + if (!pext || cfn) + dfn = genpath(t, dfn, NULL, fmtp->tf_ext); + else + dfn = dupcstr(dfn); + } + if (!dfn) { + vmterror(t, "No mem for tape data filename"); + goto badret; + } + + /* Now verify filenames don't already exist */ + if (cfn && (cf = fopen(cfn, "r"))) { + fclose(cf); + vmterror(t, "Tape control file \"%.256s\" already exists", cfn); + goto badret; + } + + if (df = fopen(dfn, "rb")) { + fclose(df); + vmterror(t, "Tape data file \"%.256s\" already exists", dfn); + goto badret; + } + + /* OK, now open for writing! */ + if (cfn && !(cf = fopen(cfn, "w"))) { + vmterror(t, "Cannot create tape control file \"%.256s\": %.80s", + cfn, os_strerror(errno)); + goto badret; + } + if (!(df = fopen(dfn, "w+b"))) { + fclose(df); + if (cfn) fclose(cf); + vmterror(t, "Cannot create tape data file \"%.256s\": %.80s", + dfn, os_strerror(errno)); + goto badret; + } + + /* All's well! Finalize state for writing */ + t->mt_filename = cfn; + t->mt_datpath = dfn; + t->mt_ctlf = cf; + t->mt_datf = df; + t->mt_ispipe = FALSE; + t->mt_writable = TRUE; + t->mt_format = fmt; + t->mt_fmtp = fmtp; + t->mt_state = TS_RWRITE; +#if 0 + switch (fmt) { + case VMT_FMT_RAW: + break; + case VMT_FMT_TPS: + break; + } +#endif + return TRUE; + + /* If come here, error of some kind. Clean up... */ + badret: + if (cf) fclose(cf); + if (df) fclose(df); + if (cfn) free(cfn); + if (dfn) free(dfn); + vmt_unmount(t); /* Ensure everything reset */ + + return FALSE; +} + +/* Mount virtual tape for reading only + Assumes any previous tape is unmounted. + */ +static int +vmt_rdmount(register struct vmtape *t, + register struct vmtattrs *ta) +{ + FILE *cf = NULL, *df = NULL; + char *cfn = NULL, *dfn = NULL; + + char *cfname = ta->vmta_ctlpath; + char *dfname = ta->vmta_datpath; + enum vmtfmt fmt = ta->vmta_fmtreq; + struct vmtfmt_s *fmtp = &vmtfmttab[fmt]; + + if (t->mt_debug) + vmterror(t, "vmt_rdmount %d=%s p=%s c=%s d=%s", + fmt, vmt_fmtname(fmt), ta->vmta_path, cfname, dfname); + + /* General plan: + If no fmt: examine pathT, then pathB, then fail. + If found, go to verify apparent format. + If fmt is ctl-type, check pathT, ctlpathT, then fail. + If found, verify fileT is ctl. Find true fmt. + If fmt is dat-type, check pathB, datpathB, then fail. + If found, verify fileB is of given fmt. + */ + t->mt_ctlf = NULL; /* Paranoia, shd already be clear */ + t->mt_datf = NULL; + t->mt_filename = NULL; + t->mt_datpath = NULL; + + if (fmt == VMT_FMT_UNK) { + /* Unknown at this point implies no explicit format spec AND + no recognizable extension. + */ + if (cf = fopen(ta->vmta_path, "r")) { + cfn = ta->vmta_path; + } else { + /* Non-ex file. If no ext in path, try all possible ctl + extensions to see if a ctl file exists, and if so, assume ctl. + */ + register struct vmtfmt_s *p; + + if (os_pathext(ta->vmta_path)) { + /* Has ext, so cannot try anything with it */ + vmterror(t, "Cannot open tape file \"%s\"", ta->vmta_path); + return FALSE; + } + + for (p = vmtfmttab; p->tf_name; ++p) { + int flags = (p->tf_flags & VMTFF_ALIAS) + ? (vmtfmttab[p->tf_flags & VMTFF_FMT].tf_flags) + : p->tf_flags; + if ((flags & VMTFF_CTL) && p->tf_ext) { + if (!(cfn = genpath(t, ta->vmta_path, NULL, p->tf_ext))) { + vmterror(t, "malloc failed for tape pathname"); + return FALSE; + } + if (cf = fopen(cfn, "r")) + break; + free(cfn); + } + } + if (!cf) { + vmterror(t, "Cannot find tape file \"%s\"", ta->vmta_path); + return FALSE; + } + fmt = VMT_FMT_CTL; /* Open, now insist on this format */ + goto havefmt; + } + /* Opened cfn with stream f, now verify it specially so if + it doesn't look like a control file we can try something else. + */ + if (fmt = fmtexamine(t, cf, VMT_FMT_CTL)) { + /* Looks like control file! */ + goto havectl; + } + /* Oops, doesn't look like a control file. Try data. */ + fclose(cf); + if (cfn != ta->vmta_path) + free(cfn); + cfn = NULL; + + if (!(df = fopen(ta->vmta_path, "rb"))) { + vmterror(t, "Cannot open binary tape file \"%s\"", ta->vmta_path); + return FALSE; + } + if (!(fmt = fmtexamine(t, df, VMT_FMT_RAW))) { /* Look for bin fmt */ + fclose(df); + vmterror(t, "Cannot determine format of tape file \"%s\"", + ta->vmta_path); + return FALSE; + } + dfn = ta->vmta_path; + } + havefmt: + if (vmtfmttab[fmt].tf_flags & (VMTFF_CTL | VMTFF_XCTL)) { + /* Control file should exist, open it if not already open */ + if (!cf) { + if (!(cf = fopen(ta->vmta_path, "r"))) { + vmterror(t, "Cannot open tape control file \"%s\"", ta->vmta_path); + goto badret; + } + cfn = ta->vmta_path; + } + if (fmtexamine(t, cf, VMT_FMT_CTL) != VMT_FMT_CTL) { + vmterror(t, "Non-text tape control file \"%s\"", cfn); + goto badret; + } + havectl: + /* Looks like a control file, suck it all in */ + if (!tdr_scan(t, cf, cfn)) { /* Read and parse the tapefile */ + vmterror(t, "Error reading tape control file"); + goto badret; /* Failed for some reason */ + } +#if VMTAPE_ITSDUMP + t->mt_fitsdate = itsdate(cf); /* Remember creation date in case */ +#endif + fclose(cf); /* Don't need tapefile open anymore */ + cf = NULL; + if (cfn == ta->vmta_path) /* Remember ctl filename */ + cfn = dupcstr(cfn); + t->mt_filename = cfn; + + fmt = t->mt_format; /* Get real format from parsed file */ + } + + /* Format known and control file snarfed if needed, + now do format-specific open of data file + */ + t->mt_format = fmt; + t->mt_fmtp = &vmtfmttab[t->mt_format]; + switch (t->mt_format) { + char *basefn; + + case VMT_FMT_RAW: + case VMT_FMT_TPE: + case VMT_FMT_TPS: + case VMT_FMT_TPC: + basefn = NULL; + if (dfn) { /* If already open, use that path */ + if (dfn == ta->vmta_path) + if (!(dfn = dupcstr(dfn))) { + vmterror(t, "malloc failed for tape data pathname"); + goto badret; + } + } else if (ta->vmta_datpath[0]) { /* Use explicit data path? */ + if (!(dfn = dupcstr(ta->vmta_datpath))) { + vmterror(t, "malloc failed for tape data pathname"); + goto badret; + } + } else { + /* No explicit data path. Attempt to figure one out either from + the opened control file (if one; should always exist for + RAW) or from the base path. + */ + if (!(basefn = cfn) && ta->vmta_path[0]) { + /* Try general path */ + if (!(dfn = dupcstr(ta->vmta_path))) { + vmterror(t, "malloc failed for tape data pathname"); + goto badret; + } + basefn = dfn; /* Try others if this one fails */ + } + } + if (!dfn && !basefn) { + vmterror(t, "No tape data pathname given"); + goto badret; + } + + if (dfn && datf_open(t, dfn, (ta->vmta_unzip ? "zrb" : "rb"))) { + ; /* Success */ + } else if (basefn) { + /* No data file spec, make one up from base filename if any */ + register struct vmtfmt_s *p; + + for (p = vmtfmttab; p->tf_name; ++p) { + if ((t->mt_format == p->tf_fmt) + || ((p->tf_flags & VMTFF_ALIAS) + && (t->mt_format == (p->tf_flags & VMTFF_FMT)) + && p->tf_ext)) { + if (!(dfn = genpath(t, basefn, NULL, p->tf_ext))) { + vmterror(t, "malloc failed for tape data pathname"); + goto badret; + } + if (datf_open(t, dfn, (ta->vmta_unzip ? "zrb" : "rb"))) + break; + free(dfn); /* This name failed, free it up */ + dfn = NULL; + } + } + } + if (!t->mt_datf) { + vmterror(t, "Cannot open tape data file \"%.256s\": %.80s", + (dfn ? dfn : basefn), os_strerror(errno)); + goto badret; + } + t->mt_datpath = dfn; + t->mt_writable = FALSE; + t->mt_state = TS_RDATA; + break; + +#if VMTAPE_ITSDUMP + case VMT_FMT_ITS: + /* Set up state for reading ITS dump file */ + t->mt_ctlf = NULL; + t->mt_datf = NULL; + t->mt_ispipe = FALSE; + t->mt_writable = FALSE; + t->mt_state = TS_THEAD; /* For now */ + break; +#endif + + default: + if ((unsigned)(t->mt_format) < VMT_FMT_N) + vmterror(t, "Unsupported tape format: %s", + vmt_fmtname(t->mt_format)); + else + vmterror(t, "Bad tape format spec: %d", (int)t->mt_format); + goto badret; + } + + /* One last little hack - if requested, skip over first N files */ + if ((ta->vmta_mask & VMTA_FSKIP) && ta->vmta_fskip) + (void) vmt_fspace(t, 0, (long) ta->vmta_fskip); + + return TRUE; + + /* If come here, error of some kind. Clean up... */ + badret: + if (cf) fclose(cf); + if (df) fclose(df); + if (cfn + && (cfn != ta->vmta_path) + && (cfn != t->mt_filename)) /* Avoid multiple free() */ + free(cfn); + if (dfn + && (dfn != ta->vmta_path) + && (dfn != t->mt_datpath)) /* Avoid multiple free() */ + free(dfn); + vmt_unmount(t); /* Ensure everything reset */ + + return FALSE; +} + + +#if CENV_SYS_UNIX +# define OSD_PATH_EXTSEPCHAR '.' +# define OSD_PATH_DIRSEPCHAR '/' +#elif CENV_SYS_MAC +# define OSD_PATH_EXTSEPCHAR '.' +# define OSD_PATH_DIRSEPCHAR ':' +#else +# error "OSD code - must define pathname chars" +#endif + +/* Return pointer to extension component of a pathname. + Assumes separated from base component by a single char; needs to be + recoded if that's not the case. + */ +static char * +os_pathext(char *path) +{ + register char *s; + + /* Find last occ of possible extension separator, and win if + it's not part of a directory component. + */ + if ((s = strrchr(path, OSD_PATH_EXTSEPCHAR)) + && (strchr(s, OSD_PATH_DIRSEPCHAR) == NULL)) + return s+1; /* Skip over separator char */ + return NULL; +} + +/* Generate newly allocated pathname from a base name using given extension. + May return same pathname if base already has that extension. + If "oldext" given, old ext must match or new ext is simply appended. + */ +static char * +genpath(struct vmtape *t, + char *base, + char *oldext, + char *newext) +{ + register char *fn, *ext, *s; + register int blen; + + if ((s = os_pathext(base)) /* If base has extension */ + && (!oldext /* and no criteria */ + || (strcmp(s, oldext)==0))) /* or meets criteria */ + blen = (s - base) - 1; /* then replace sep-char & ext! */ + else blen = strlen(base); /* No ext, append to whole name */ + + if (fn = malloc(blen+1+strlen(newext)+1)) { + memcpy(fn, base, blen); + s = fn + blen; + *s++ = OSD_PATH_EXTSEPCHAR; + strcpy(s, newext); + } + + if (t->mt_debug) + vmterror(t, "genpath b=%s o=%s e=%s -> %s", + base, oldext ? oldext : "(null)", newext, fn); + + return fn; +} + + +/* Examine file contents for clues to its format. + Returns -1 if hits an I/O error. + Otherwise returns its guess as to file's format, which will be 0 + (i.e. VMT_FMT_UNK) if it can't tell or was asked to give up early. + The "fmt" arg allows a little control: + VMT_FMT_UNK (0) to try all known formats, + VMT_FMT_RAW to check for all binary formats, + VMT_FMT_XXX to check for that specific format (currently only + VMT_FMT_CTL, VMT_FMT_TPS, VMT_FMT_TPE will work). + + Note that VMT_FMT_CTL is only guaranteed to work with a text stream, and + VMT_FMT_RAW with a binary stream. VMT_FMT_UNK may fail to work correctly on + a system where text and binary streams are different (such as TOPS-20!). + If that becomes necessary, the fix is to try the file in both modes. + + In general VMT_FMT_TPC cannot be reliably distinguished from other formats, + although it is possible. + */ +static int +fmtexamine(struct vmtape *t, FILE *f, int fmt) +{ + register int i, n; + uint32 cnt, cnt1; + unsigned char buff[10]; /* Read first N chars */ + unsigned char buf2[4]; + + memset((void *)buff, 0, sizeof(buff)); + n = fread((void *)buff, 1, sizeof(buff), f); + if (n == -1) { + vmterror(t, "Error determining tape file format: %s", + os_strerror(errno)); + return -1; + } + rewind(f); + + /* Only possibilities for all sizes less than 10 are: + 0 = anything + 1 = - + 2 = one TPC tapemark + 3 = - + 4 = one TPS/TPE tapemark, two TPC tapemarks, or one TPC record + 5 = - + 6 = 3 TPC tapemarks, one TPC tapemark & record, or one TPC record + 7 = - + 8 = two TPS/TPE tapemarks, or some combos of TPC records/tapemarks + 9 = one TPE record + */ + if (n != sizeof(buff)) { + switch (n) { + case 2: /* Drop thru for TPC check */ + break; + case 4: + if (VMT_TPS_COUNT(&buff[0]) == 0) { + /* File is single TPS/TPE or double TPC tapemark */ + if ( (fmt == VMT_FMT_UNK) + || (fmt == VMT_FMT_RAW)) + return VMT_FMT_TPS; + if ( (fmt == VMT_FMT_TPE) + || (fmt == VMT_FMT_TPS) + || (fmt == VMT_FMT_TPC)) + return fmt; + return VMT_FMT_UNK; /* Not what we wanted */ + } + break; + case 6: /* Drop thru for TPC check */ + break; + case 8: + if ((VMT_TPS_COUNT(&buff[0]) == 0) + && (VMT_TPS_COUNT(&buff[4]) == 0)) { + /* File is double TPS/TPE tapemark (or quad TPC) */ + if ( (fmt == VMT_FMT_UNK) + || (fmt == VMT_FMT_RAW)) + return VMT_FMT_TPS; + if ( (fmt == VMT_FMT_TPE) + || (fmt == VMT_FMT_TPS) + || (fmt == VMT_FMT_TPC)) + return fmt; + return VMT_FMT_UNK; /* Not what we wanted */ + } + break; + case 9: /* I'm sorry I even wrote this */ + if ((VMT_TPS_COUNT(&buff[0]) == 1) + && (VMT_TPS_COUNT(&buff[5]) == 1)) { + /* File is TPE 1-byte record */ + if ( (fmt == VMT_FMT_UNK) + || (fmt == VMT_FMT_RAW) + || (fmt == VMT_FMT_TPE)) + return VMT_FMT_TPE; + return VMT_FMT_UNK; /* Not what we wanted */ + } + break; + default: /* 0, 1, 3, 5, 7 */ + return VMT_FMT_UNK; + } + /* Come here if length is even and TPS/TPE didn't work */ + if ( (fmt == VMT_FMT_UNK) + || (fmt == VMT_FMT_RAW) + || (fmt == VMT_FMT_TPC)) { + /* Check for TPC */ + for (i = 0; i < n; i += 2) { + if (cnt = VMT_TPC_COUNT(&buff[i])) + i += (cnt&01) ? (cnt+1) : cnt; + } + if (i == n) /* Verify no overrun */ + return VMT_FMT_TPC; + } + return VMT_FMT_UNK; + } + + /* Check for ASCII control file */ + for (i = 0; i < n; ++i) + if (!isspace(buff[i]) && !isprint(buff[i])) + break; + if (i == n) { + /* Looks like a control file */ + if ( (fmt == VMT_FMT_UNK) + || (fmt == VMT_FMT_CTL)) + return VMT_FMT_CTL; + return VMT_FMT_UNK; /* Not what we wanted */ + } + + if ((fmt == VMT_FMT_UNK) || (fmt != VMT_FMT_CTL)) { + /* Not ASCII, check for TPS. 32-bit count must be replicated at end + of record, and impose 16-bit length sanity check. + XXX: If count is odd, we have an opportunity to distinguish between + TPS (which pads) and TPE (which doesn't). + */ + cnt = cnt1 = VMT_TPS_COUNT(&buff[0]) & VMT_TPS_CNTF; + cnt1 += (cnt1 & 01); /* Round up to 2-byte boundary */ + if ((cnt1 <= (1L<<16)) /* Limit to 16 bits of record length */ + && (fseek(f, (long)cnt1+4, SEEK_SET) == 0) + && (fread((void *)buf2, 1, sizeof(buf2), f) == sizeof(buf2)) + && (memcmp((void *)buff, (void *)buf2, sizeof(buf2)) == 0)) { + /* Success */ + rewind(f); + + if ( (fmt == VMT_FMT_UNK) + || (fmt == VMT_FMT_RAW)) + return VMT_FMT_TPS; + if ( (fmt == VMT_FMT_TPE) + || (fmt == VMT_FMT_TPS)) + return fmt; + return VMT_FMT_UNK; /* Not what we wanted */ + } + } + + /* Give up. No test for VMT_FMT_TPC as it could look like + almost anything. + */ + rewind(f); + return VMT_FMT_UNK; +} + + +enum vmtfmt +vmt_strtofmt(char *str) +{ + register int i; + + for (i = 0; i < VMT_FMT_N; ++i) { + if (vmtfmttab[i].tf_name + && (strcasecmp(str, vmtfmttab[i].tf_name)==0)) { + if (vmtfmttab[i].tf_flags & VMTFF_ALIAS) + return vmtfmttab[i].tf_flags & VMTFF_FMT; + return i; + } + } + return 0; +} + +enum vmtfmt +vmt_exttofmt(char *ext) +{ + register int i; + + for (i = 0; i < VMT_FMT_N; ++i) { + if (vmtfmttab[i].tf_ext + && (strcasecmp(ext, vmtfmttab[i].tf_ext)==0)) { + if (vmtfmttab[i].tf_flags & VMTFF_ALIAS) + return vmtfmttab[i].tf_flags & VMTFF_FMT; + return i; + } + } + return 0; +} + +#if VMTAPE_ARGSTR /* Include optional mount arg string parsing */ + +#include "prmstr.h" + +#define VMTMNT_PARAMS \ + prmdef(VMTP_HARD, "hard"), /* Device, not virtual tape */\ + prmdef(VMTP_DEBUG, "debug"), /* Virtual: TRUE for debug output */\ + prmdef(VMTP_FMT, "fmt"), /* Virtual: format= */\ + prmdef(VMTP_MODE, "mode"), /* Virtual: mode= */\ + prmdef(VMTP_PATH, "path"), /* Virtual: path= */\ + prmdef(VMTP_CPATH, "cpath"), /* Virtual: cpath= */\ + prmdef(VMTP_RPATH, "rpath"), /* Virtual: rpath= */\ + prmdef(VMTP_UNZIP, "unzip"), /* Virtual: TRUE to allow uncompression */\ + prmdef(VMTP_INMEM, "inmem"), /* Virtual: TRUE to suck into memory */\ + prmdef(VMTP_FSKIP, "fskip"), /* Virtual: fskip=# files to skip */\ + prmdef(VMTP_RO, "ro"), /* Same as mode=read */\ + prmdef(VMTP_RW, "rw"), /* Same as mode=create */\ + prmdef(VMTP_READ, "read"), /* Same as mode=read */\ + prmdef(VMTP_CREATE,"create"),/* Same as mode=create */\ + prmdef(VMTP_UPDATE,"update") /* Same as mode=update */ + +enum { +# define prmdef(i,s) i + VMTMNT_PARAMS +# undef prmdef +}; + +static char *vmtprmtab[] = { +# define prmdef(i,s) s + VMTMNT_PARAMS +# undef prmdef + , NULL +}; + +static int +vmtparmode(char *cp, int *mode) +{ + if (strcasecmp(cp, "read" )==0) *mode = VMT_MODE_RDONLY; + else if (strcasecmp(cp, "create")==0) *mode = VMT_MODE_CREATE; + else if (strcasecmp(cp, "update")==0) *mode = VMT_MODE_UPDATE; + else return FALSE; + return TRUE; +} + +static int +vmtparfmt(char *cp, enum vmtfmt *fmt) +{ + return (*fmt = vmt_strtofmt(cp)) ? TRUE : FALSE; +} + + +int +vmt_attrparse(register struct vmtape *t, + register struct vmtattrs *ta, + char *argstr) +{ + int i, ret = TRUE; + struct prmstate_s prm; + char buff[200]; + long lval; + + if (!argstr) + return TRUE; + prm_init(&prm, buff, sizeof(buff), + argstr, strlen(argstr), + vmtprmtab, sizeof(vmtprmtab[0])); + while ((i = prm_next(&prm)) != PRMK_DONE) { + switch (i) { + case PRMK_NONE: + vmterror(t,"Unknown mount parameter \"%s\"", prm.prm_name); + ret = FALSE; + continue; + case PRMK_AMBI: + vmterror(t,"Ambiguous mount parameter \"%s\"", prm.prm_name); + ret = FALSE; + continue; + default: /* Handle matches not supported */ + vmterror(t,"Unsupported mount parameter \"%s\"", prm.prm_name); + ret = FALSE; + continue; + + /* --------- specific args ----------- */ + + case VMTP_FMT: /* Virtual: format= */ + if (!prm.prm_val || !vmtparfmt(prm.prm_val, &ta->vmta_fmtreq)) + break; + ta->vmta_mask |= VMTA_FMTREQ; + continue; + + case VMTP_MODE: /* Virtual: mode= */ + if (!prm.prm_val || !vmtparmode(prm.prm_val, &ta->vmta_mode)) + break; + ta->vmta_mask |= VMTA_MODE; + continue; + + case VMTP_FSKIP: /* Virtual: fskip= */ + if (!prm.prm_val || !s_todnum(prm.prm_val, &lval)) + break; + ta->vmta_fskip = lval; + ta->vmta_mask |= VMTA_FSKIP; + continue; + +#define VMTPMODE(v) \ + if (prm.prm_val) break; /* No arg allowed */\ + ta->vmta_mode = (v); \ + ta->vmta_mask |= VMTA_MODE + + case VMTP_RO: /* Same as mode=read */ + case VMTP_READ: /* Same as mode=read */ + VMTPMODE(VMT_MODE_RDONLY); + continue; + + case VMTP_RW: /* Compatibility: Create, not Update */ + case VMTP_CREATE: /* Same as mode=create */ + VMTPMODE(VMT_MODE_CREATE); + continue; + + case VMTP_UPDATE: /* Same as mode=update */ + VMTPMODE(VMT_MODE_UPDATE); + continue; +#undef VMTPMODE + +#define VMTPBOOL(v, default) \ + (prm.prm_val ? s_tobool(prm.prm_val, &(v)) \ + : (((v) = (default)), TRUE)) + + case VMTP_UNZIP: /* Virtual: TRUE to allow uncompression */ + if (!VMTPBOOL(ta->vmta_unzip, TRUE)) + break; + ta->vmta_mask |= VMTA_UNZIP; + continue; + + case VMTP_INMEM: /* Virtual: TRUE to suck into memory */ + if (!VMTPBOOL(ta->vmta_inmem, TRUE)) + break; + ta->vmta_mask |= VMTA_INMEM; + continue; + + case VMTP_DEBUG: /* Virtual: TRUE for debug output */ + /* Note this parameter operates immediately on the vmtape + structure itself, not the vmtattrs struct! + */ + if (!VMTPBOOL(t->mt_debug, TRUE)) + break; + ta->vmta_mask |= VMTA_INMEM; + continue; +#undef VMTPBOOL + +#define VMTPSTR(v,siz,attr) \ + if (!prm.prm_val) break; \ + if (strlen(prm.prm_val) >= siz) { \ + vmterror(t, "mount param \"%s\" too long (max %d)", \ + prm.prm_name, (int)siz); \ + ret = FALSE; \ + continue; \ + } else strcpy((v), prm.prm_val); \ + ta->vmta_mask |= (attr); + + case VMTP_HARD: /* Device, not actually virtual tape */ + if (!prm.prm_val) /* No arg => default */ + prm.prm_val = "*"; + VMTPSTR(ta->vmta_dev, sizeof(ta->vmta_dev), VMTA_DEV); + continue; + + case VMTP_PATH: /* Virtual: path= */ + VMTPSTR(ta->vmta_path, sizeof(ta->vmta_path), VMTA_PATH); + continue; + + case VMTP_CPATH: /* Virtual: cpath= */ + VMTPSTR(ta->vmta_ctlpath, sizeof(ta->vmta_ctlpath), VMTA_CTLPATH); + continue; + + case VMTP_RPATH: /* Virtual: rpath= */ + VMTPSTR(ta->vmta_datpath, sizeof(ta->vmta_datpath), VMTA_DATPATH); + continue; +#undef VMTPSTR + } + + /* Break out to here for generic arg complaints */ + ret = FALSE; + if (prm.prm_val) + vmterror(t,"mount param \"%s\" has bad value: \"%s\"", + prm.prm_name, prm.prm_val); + else + vmterror(t,"mount param \"%s\" has missing value", + prm.prm_name); + } + + /* Param string all done, do followup checks or cleanup */ + + return ret; +} + + + +#endif /* VMTAPE_ARGSTR */ + +#if VMTAPE_POPEN +static struct vmtpipent { + char *zext; + int zextlen; + char *pcmd; +} vmtpipetab[] = { +# define UNGZIPCMD "gzip -dc " /* Uncompress to standard output */ +# define UNBZIPCMD "bzip2 -dc " /* Uncompress to standard output */ +# define VMTPIPECMD_MAX sizeof(UNBZIPCMD) /* Longest command */ + { ".Z", 2, UNGZIPCMD}, /* Try standard compress extension */ + { ".z", 2, UNGZIPCMD}, /* Try new GZIP convention */ + { ".gz", 3, UNGZIPCMD}, /* Try newer GZIP convention */ + { "-z", 2, UNGZIPCMD}, /* Try other GZIP convention */ + { ".bz", 3, UNBZIPCMD}, /* Try BZIP convention */ + { ".bz2",4, UNBZIPCMD}, /* Try newer BZIP2 convention */ + { NULL, 0, NULL} +}; + +#define VMTPIPECMDQUOT ";&()|^<>\n \t#'\"\\*?[]$" + +static int +popenquote(char *str, char *qchrs, int max) +{ + register char *cp, *qp; + register size_t i; + char qbuf[OS_MAXPATHLEN*2+1]; + + if (!(cp = strpbrk(str, qchrs))) + return TRUE; /* No actual quoting needed */ + i = cp - str; + if (max > (sizeof(qbuf)-1)) + max = sizeof(qbuf)-1; + + /* Ugh, must quote. */ + qp = qbuf; + cp = str; + for (;;) { + if ((max -= (i+2)) <= 0) + return FALSE; /* Oops, too long */ + if (i) { /* First copy in-between chars */ + memcpy(qp, cp, i); + qp += i, cp += i; + } + *qp++ = '\\'; /* Insert backslash as quoter */ + *qp++ = *cp++; /* Then copy quoted char */ + if (*cp == '\0') + break; + i = strcspn(cp, qchrs); + } + *qp++ = '\0'; + memcpy(str, qbuf, (qp-qbuf)); /* Copy back over original string */ + return TRUE; +} +#endif /* VMTAPE_POPEN */ + +static int +datf_open(register struct vmtape *t, + char *fname, char *mode) +{ + FILE *f; + int zmode; + + if (*mode == 'z') { + zmode = TRUE; + ++mode; + } else zmode = FALSE; + + /* Attempt to open filespec as given */ + t->mt_ispipe = FALSE; + t->mt_datf = fopen(fname, mode); + + if (t->mt_debug) + vmterror(t, "datf_open %s %s", + fname, (t->mt_datf ? "succeeded" : "failed")); + + +#if VMTAPE_POPEN + if (zmode) + { + int flen = strlen(fname); + register int i; + register char *cp, *ecp; + struct vmtpipent *p; + int bzipf = FALSE; + char cmdbuf[VMTPIPECMD_MAX+(OS_MAXPATHLEN*2)]; /* *2 to allow quoting */ + + if ((flen + VMTPIPECMD_MAX+2) >= sizeof(cmdbuf)-1) { + /* Filespec too big, cannot hack */ + return (t->mt_datf ? TRUE : FALSE); + } + cp = cmdbuf + VMTPIPECMD_MAX; + + /* Do checking for unzip hackery! */ + if (t->mt_datf) { + /* See whether file just opened has an extension that indicates + compression. If so, re-open it through an uncompress pipe. + */ + ecp = fname + flen; + for (p = vmtpipetab; p->zext; ++p) { + if ((flen > p->zextlen) + && (strcmp(ecp - p->zextlen, p->zext)==0)) { + /* Found it! */ + strcpy(cp, fname); + goto dounzip; + } + } + return TRUE; + } + + /* Furnished filespec didn't exist. Try adding known compression + ** extensions, and if found, invoke subprocess to uncompress it. + */ + memcpy(cp, fname, flen); /* Set up filename */ + ecp = cp + flen; /* Start of added extension */ + + for (p = vmtpipetab; p->zext; ++p) { + strcpy(ecp, p->zext); + t->mt_datf = fopen(cp, mode); + if (t->mt_debug) + vmterror(t, "datf_open %s %s", + cp, (t->mt_datf ? "succeeded" : "failed")); + + if (t->mt_datf) + goto dounzip; + } + return FALSE; /* Couldn't find a match, give up */ + + /* File exists! Fire up an unzipper... */ + dounzip: + fclose(t->mt_datf); + t->mt_datf = NULL; + + memset(cmdbuf, ' ', VMTPIPECMD_MAX); /* Init command with spaces */ + memcpy(cmdbuf, p->pcmd, strlen(p->pcmd)); /* Prefix command */ + if (!popenquote(cp, VMTPIPECMDQUOT, sizeof(cmdbuf)-VMTPIPECMD_MAX)) + return FALSE; /* Couldn't quote, shouldn't happen */ + + if (t->mt_datf = popen(cmdbuf, (*mode == 'r') ? "r" : "w")) + t->mt_ispipe = TRUE; + } +#endif /* VMTAPE_POPEN */ + + return (t->mt_datf ? TRUE : FALSE); +} + + +static void +datf_close(register struct vmtape *t) +{ + if (t->mt_datf) { + if (vmt_iswritable(t)) { /* If may have output something, */ + fflush(t->mt_datf); + } + +#if VMTAPE_POPEN + if (t->mt_ispipe) { + pclose(t->mt_datf); + } else +#endif + fclose(t->mt_datf); + } + t->mt_ispipe = FALSE; + t->mt_datf = NULL; +} + +/* Tape positioning functions */ + +/* Utility subroutines must return a value as follows: +*/ +enum vmtfsret { + VMT_FSRET_ERR=-1, /* -1 = Error */ + VMT_FSRET_NONE, /* 0 = no files spaced, at BOT or EOT */ + VMT_FSRET_ONE, /* 1 = one file spaced, no more */ + VMT_FSRET_MORE /* 2 = one file spaced, may be more */ +}; + +#if VMTAPE_ITSDUMP +static enum vmtfsret its_filebwd(struct vmtape *t); +static enum vmtfsret its_filefwd(struct vmtape *t); +#endif +static int tps_recfwd(struct vmtape *t); +static int tps_recbwd(struct vmtape *t); +static int tpc_recfwd(struct vmtape *t); +static int raw_recfwd(struct vmtape *t); +static int raw_recbwd(struct vmtape *t); +static void rawposfix(struct vmtape *t); +static int td_posstate(struct vmttdrdef *td); + +int +vmt_rewind(register struct vmtape *t) +{ + int res; + + if (!vmt_ismounted(t)) + return FALSE; /* No tape mounted */ + + t->mt_bot = TRUE; + t->mt_eof = t->mt_eot = FALSE; + + switch(t->mt_format) { + + default: + return FALSE; /* Unknown format */ +#if VMTAPE_ITSDUMP + case VMT_FMT_ITS: + t->mt_fcur = NULL; /* Back to start of filedefs */ + datf_close(t); /* Maybe check for error? */ + t->mt_cnt = 4; + t->mt_ptr = itsdumphead; + t->mt_state = TS_THEAD; + break; +#endif + + case VMT_FMT_RAW: + t->mt_tdr.tdcur = NULL; /* Clear vmtrecdefs */ + t->mt_tdr.tdrncur = 0; + /* Drop thru to common code */ + + case VMT_FMT_TPE: + case VMT_FMT_TPS: + case VMT_FMT_TPC: + res = TRUE; + if (vmt_iswritable(t)) { /* If open for reading/writing, */ + res = vmt_eot(t); /* Flush buffs, dump out any tape dir so far */ + } + t->mt_state = TS_RDATA; + if (t->mt_datf) { + rewind(t->mt_datf); + if (ferror(t->mt_datf) || !res) + return FALSE; + } + break; + } + return TRUE; +} + +/* Space forward or backward N records. +** A tapemark stops the spacing without being included in the count +** (this is how the TM03 behaves). +** Sets mt_frames to the # of records spaced over. +** Returns FALSE if there was some internal error; "data" errors are ignored. +*/ + +int +vmt_rspace(register struct vmtape *t, + int revf, /* 0 forward, else backward */ + register unsigned long cnt) +{ + unsigned long origcnt = cnt; + + if (!vmt_ismounted(t)) + return FALSE; + + switch (t->mt_format) { +#if VMTAPE_ITSDUMP + case VMT_FMT_ITS: + if (revf) { /* Can only backspace over one tapemark */ + /* If just read a tapemark, back up over it. */ + if (t->mt_eof) { + t->mt_eof = FALSE; + t->mt_state = TS_FEOF; + t->mt_frames = 0; + return TRUE; + } else if (!t->mt_bot) { + /* Otherwise, hack it by backing up to start of current file */ + (void) its_filebwd(t); + t->mt_frames = 1; + } + return TRUE; + } else do { + w10_t w; + vmt_iobeg(t, FALSE); + while (its_wget(t, &w)); /* Space forward a record */ + if (t->mt_frames) + --cnt; + if (t->mt_eof || t->mt_eot) + break; + } while (cnt); + break; +#endif /* VMTAPE_ITSDUMP */ + case VMT_FMT_RAW: + t->mt_eof = t->mt_eot = t->mt_bot = FALSE; + rawposfix(t); /* Canonicalize current position */ + if (revf) { + while (raw_recbwd(t) > 0 + && !t->mt_eof && !t->mt_bot && --cnt) ; + } else + while (raw_recfwd(t) > 0 + && !t->mt_eof && !t->mt_eot && --cnt) ; + break; + case VMT_FMT_TPE: + case VMT_FMT_TPS: + t->mt_eof = t->mt_eot = t->mt_bot = FALSE; + if (revf) { + while (tps_recbwd(t) > 0 + && !t->mt_eof && !t->mt_bot && --cnt) ; + } else + while (tps_recfwd(t) > 0 + && !t->mt_eof && !t->mt_eot && --cnt) ; + break; + + case VMT_FMT_TPC: + /* Can space forward but not backward. */ + if (revf) { + /* XXX Attempt hackery to allow backspacing over 1 tapemark? + */ + if ( 0 /* t->mt_eof */) { /* If last thing read was an EOF */ + + } else { + t->mt_frames = 0; + return FALSE; + } + } else + t->mt_eof = t->mt_eot = t->mt_bot = FALSE; + while (tpc_recfwd(t) > 0 + && !t->mt_eof && !t->mt_eot && --cnt) ; + break; + } + t->mt_frames = origcnt - cnt; + return TRUE; +} + +/* Space forward or backward N files (tapemarks). +** Sets mt_frames to the # of files spaced over (normally the # of tapemarks +** seen, but will include BOT/EOT if tape moved to get there). +** Returns FALSE if there was some internal error; "data" errors are ignored. +*/ +int +vmt_fspace(register struct vmtape *t, + int dir, /* 0 forward, else backward */ + unsigned long cnt) +{ + long origcnt = cnt; + enum vmtfsret (*foo)(struct vmtape *) = NULL; + int (*recmove)(struct vmtape *); + + if (t->mt_debug) + vmterror(t, "vmt_fspace %ld %s", cnt, (dir ? "rev" : "fwd")); + + t->mt_frames = 0; + + if (!vmt_ismounted(t)) + return FALSE; + + switch (t->mt_format) { + case VMT_FMT_RAW: + rawposfix(t); /* Ensure in canonical state */ + recmove = dir ? raw_recbwd : raw_recfwd; + break; + case VMT_FMT_TPE: + case VMT_FMT_TPS: + recmove = dir ? tps_recbwd : tps_recfwd; + break; + case VMT_FMT_TPC: + if (dir) /* TPC cannot go backwards */ + return FALSE; + recmove = tpc_recfwd; + break; +#if VMTAPE_ITSDUMP + case VMT_FMT_ITS: + foo = dir ? its_filebwd : its_filefwd; + break; +#endif + default: + return FALSE; + } + + /* Do general loop */ + do { + register int fsret, res; + if (foo) + fsret = (*foo)(t); + else if (dir) { + /* Generic backward loop */ + t->mt_eof = t->mt_eot = t->mt_bot = FALSE; + while ((res = (*recmove)(t)) > 0 && !t->mt_eof && !t->mt_bot) ; + if (res <= 0) + fsret = (res < 0) ? VMT_FSRET_ERR : VMT_FSRET_NONE; + else + fsret = t->mt_bot ? VMT_FSRET_ONE : VMT_FSRET_MORE; + + } else { + /* Generic forward loop */ + t->mt_eof = t->mt_eot = t->mt_bot = FALSE; + while ((res = (*recmove)(t)) > 0 && !t->mt_eof && !t->mt_eot) ; + if (res <= 0) + fsret = (res < 0) ? VMT_FSRET_ERR : VMT_FSRET_NONE; + else + fsret = t->mt_eot ? VMT_FSRET_ONE : VMT_FSRET_MORE; + } + + switch (fsret) { + default: + case VMT_FSRET_ERR: + return FALSE; + case VMT_FSRET_NONE: + return TRUE; + case VMT_FSRET_ONE: + t->mt_frames++; + return TRUE; + case VMT_FSRET_MORE: + t->mt_frames++; + break; + } + } while (--cnt); + return TRUE; +} + +static int +vmtflushinp(register struct vmtape *t, + size_t reclen) +{ + size_t cnt, res; + unsigned char buf[1024]; + + for (; reclen > 0; reclen -= cnt) { + cnt = (reclen < sizeof(buf) ? reclen : sizeof(buf)); + if ((res = fread(buf, 1, cnt, t->mt_datf)) != cnt) { + vmterror(t, "input rec error: %ld != %ld, data file \"%.256s\"", + res, cnt, + t->mt_filename); + vmtseterr(t); + return FALSE; + } + } + return TRUE; +} + +/* Format-specific positioning functions */ + +#if VMTAPE_ITSDUMP +static struct vmtfildef *fd_prevget(register struct vmtape *t); +static struct vmtfildef *fd_nextget(register struct vmtape *t); + +static enum vmtfsret +its_filebwd(register struct vmtape *t) +{ + switch (t->mt_state) { + case TS_THEAD: + vmt_rewind(t); + return VMT_FSRET_NONE; + + case TS_FHEAD: + case TS_FDATA: + case TS_FEOF: + if (fd_prevget(t)) + t->mt_state = TS_FEOF; + else + vmt_rewind(t); + break; + + case TS_FNEXT: + t->mt_eof = TRUE; + t->mt_state = TS_FEOF; + break; + + case TS_EOT: + t->mt_eof = TRUE; + t->mt_state = TS_FEOF; + break; + default: + return VMT_FSRET_ERR; + } + return VMT_FSRET_MORE; +} + +static enum vmtfsret +its_filefwd(register struct vmtape *t) +{ + switch (t->mt_state) { + case TS_THEAD: + case TS_FHEAD: + case TS_FDATA: + case TS_FEOF: + t->mt_eof = TRUE; + t->mt_state = TS_FNEXT; + break; + + case TS_FNEXT: + fd_nextget(t); /* Move to next file, changes state */ + break; /* properly if no more files */ + + case TS_EOT: + t->mt_eot = TRUE; + return VMT_FSRET_NONE; + + default: + return VMT_FSRET_ERR; + } + return VMT_FSRET_MORE; +} + +static struct vmtfildef * +fd_prevget(register struct vmtape *t) +{ + if (t->mt_fcur) { + if (t->mt_fcur == t->mt_fdefs) { + t->mt_fcur = NULL; + } else + t->mt_fcur = t->mt_fcur->fdprev; + } + return t->mt_fcur; +} + +static struct vmtfildef * +fd_nextget(register struct vmtape *t) +{ + if (!(t->mt_fcur)) + t->mt_fcur = t->mt_fdefs; + else { + t->mt_fcur = t->mt_fcur->fdnext; + if (t->mt_fcur == t->mt_fdefs) { /* Back at start? */ + t->mt_fcur = t->mt_fcur->fdprev; /* Yep, back up */ + t->mt_eof = TRUE; /* No more files */ + t->mt_state = TS_EOT; /* So this is really end */ + return NULL; + } + } + return t->mt_fcur; +} + +#endif /* VMTAPE_ITSDUMP */ + + +/* ================================================= + RAW format positioning + */ + +static struct vmtrecdef *rd_prevget(struct vmtape *t); + + +/* Move backwards over raw tape's previous record/tapemark. +** rawposfix() must have been called prior to one or more calls +** of this routine. +*/ +static struct vmtrecdef * +rd_prevget(register struct vmtape *t) +{ + register struct vmttdrdef *td = &t->mt_tdr; + register struct vmtrecdef *rd; + + if (!(rd = td->tdcur)) { /* At EOT? */ + if (!(rd = td->tdrd)) { + return NULL; /* Cannot move back, still at EOT (& BOT) */ + } + /* Back up from EOT. rdprev will point to last record, so + ** dropping through does the right thing. + */ + } else { + /* Back up from current location */ + if (--(td->tdrncur) >= 0) /* If all goes well, */ + return rd; /* simply return this vmtrecdef! */ + + /* Oops, must get previous vmtrecdef */ + if (rd == td->tdrd) { /* If this vmtrecdef is already first */ + td->tdrncur = 0; /* stay at BOT */ + return NULL; /* and say couldn't move back */ + } + } + + /* Back up to end of previous vmtrecdef */ + td->tdcur = rd = rd->rdprev; /* Must back up to previous vmtrecdef */ + if ((td->tdrncur = rd->rdcnt) != 0) /* Unless it's a tapemark, */ + --(td->tdrncur); /* move over last record in it. */ + + return rd; +} + + +/* Canonicalize raw tape position prior to doing something +** Ensures that current position is correctly set up based on +** values of tdcur and tdrncur. Ignores current state but sets it +** as if about to read next record or tapemark. +*/ +static void +rawposfix(register struct vmtape *t) +{ + register struct vmttdrdef *td = &t->mt_tdr; + + if (!td->tdcur) { /* If no current record def */ + if (!td->tdrncur) { /* Check BOT or EOT */ + if (!(td->tdcur = td->tdrd)) /* BOT, try to start there */ + td->tdrncur = 1; /* Ugh, make it EOT. */ + } + } else if (td->tdrncur && td->tdrncur >= td->tdcur->rdcnt) { + /* Need next record def */ + td->tdrncur = 0; /* Read 1st record of next def */ + if ((td->tdcur = td->tdcur->rdnext) + == td->tdrd) { + td->tdcur = NULL; /* Oops, reached EOT, so clobber. */ + td->tdrncur = 1; /* Distinguish from BOT. */ + } + } + + /* Now set tape state appropriately. */ + t->mt_state = td_posstate(td); +} + +/* Return raw tape state (a TS_ value) based on its current +** logical position. +*/ +static int +td_posstate(register struct vmttdrdef *td) +{ + return + (td->tdcur == NULL) ? TS_EOT /* Next read returns EOT */ + : (td->tdcur->rdlen == 0 + || td->tdcur->rdcnt == 0) ? TS_REOF /* Next read returns EOF */ + : TS_RDATA; /* Next read returns data */ +} + +/* Space 1 record forward. +** rawposfix() must have been called prior to one or more calls +** of this routine. +** Returns 1 (true) on success, +** 0 (false) if cannot space forward (ie already at EOT) +** -1 if error (otherwise same as 0) +** A tapemark causes a success return with mt_eof set TRUE. +*/ +static int +raw_recfwd(register struct vmtape *t) +{ + register struct vmtrecdef *rd; + + if (!(rd = t->mt_tdr.tdcur)) { /* If at EOT */ + t->mt_eot = TRUE; + return 0; + } + if (rd->rdlen == 0 || rd->rdcnt == 0) + t->mt_eof = TRUE; + else if (t->mt_ispipe) + (void) vmtflushinp(t, (size_t)(rd->rdlen)); + + t->mt_tdr.tdrncur++; /* Bump to next record */ + rawposfix(t); /* Canonicalize and set possibly new state */ + return 1; +} + +static int +raw_recbwd(register struct vmtape *t) +{ + register struct vmtrecdef *rd; + + if (!(rd = rd_prevget(t))) { + t->mt_bot = TRUE; /* Couldn't move back, so fail */ + } else if (t->mt_tdr.tdrncur == 0) { + if (rd->rdcnt == 0) /* Backed up over a tapemark? */ + t->mt_eof = TRUE; + if (rd == t->mt_tdr.tdrd) + t->mt_bot = TRUE; + } + t->mt_state = td_posstate(&t->mt_tdr); + return rd ? 1 : 0; +} + +/* ================================================= + TPS format positioning + */ + +/* Space 1 record forward. +** Returns 1 on success, 0 failure, -1 error. +*/ +static int +tps_recfwd(register struct vmtape *t) +{ + long ioptr; + int res; + uint32 len; + unsigned char header[4]; + + /* Ensure set up for reading */ + if (t->mt_state != TS_RDATA) + vmt_iobeg(t, FALSE); + + if ((res = fread(header, 1, sizeof(header), t->mt_datf)) + != sizeof(header)) { + if (feof(t->mt_datf)) { /* If at EOT, ignore even partial header */ + t->mt_eot = TRUE; + return 0; /* Fail */ + } else { + vmterror(t, "rsp partial header: %d, data file \"%.256s\"", + res, + t->mt_filename); + /* What else to do?? Read/write gubbish... */ + vmtseterr(t); + return -1; + } + } + len = (header[3]<<24) | (header[2]<<16) | (header[1]<<8) | header[0]; + if (len & VMT_TPS_ERRF) { + /* Error - for now, skip over it anyway */ + len &= MASK31; + } + if (len == 0) { + t->mt_eof = TRUE; /* Indicate hit tapemark */ + return 1; /* and succeed */ + } + + /* Skip over data. Don't bother checking post-data header, to allow + moving over possible bad stuff. Also ignore fseek error, next + i/o attempt will catch it. + */ + if ((len & 01) && (t->mt_format == VMT_FMT_TPS)) + len++; /* TPS pads up, VMT_FMT_TPE doesn't */ + ioptr = len + sizeof(header); + if (t->mt_ispipe) + return vmtflushinp(t, ioptr) ? 1 : -1; + else { + if (fseek(t->mt_datf, ioptr, SEEK_CUR) != 0) { + vmterror(t, "rsp seek error: %d, data file \"%.256s\"", + errno, + t->mt_filename); + /* What else to do?? Read/write gubbish... */ + vmtseterr(t); + return -1; + } + } + return 1; +} + +static int +tps_recbwd(register struct vmtape *t) +{ + long ioptr; + int res; + uint32 len, hlen; + unsigned char header[4]; + + /* Ensure set up for reading */ + if (t->mt_state != TS_RDATA) + vmt_iobeg(t, FALSE); + + if (t->mt_ispipe) { + vmterror(t, "Cannot backspace on pipe - data file \"%.256s\"", + t->mt_datpath); + /* What else to do?? Read/write gubbish... */ + vmtseterr(t); + return -1; + } + + if (fseek(t->mt_datf, -sizeof(header), SEEK_CUR)) { + if ((ioptr = ftell(t->mt_datf)) == 0) { + t->mt_bot = TRUE; + return 0; + } + vmterror(t, "bsp seek/tell failure: %ld, data file \"%.256s\"", + (long)ioptr, + t->mt_datpath); + /* What else to do?? Read/write gubbish... */ + vmtseterr(t); + return -1; + + } + if ((res = fread(header, 1, sizeof(header), t->mt_datf)) + != sizeof(header)) { + if (feof(t->mt_datf)) { /* If at EOT, ignore even partial header */ + t->mt_eot = TRUE; + return 0; /* Fail */ + } else { + vmterror(t, "rsp partial trailer: %d, data file \"%.256s\"", + res, + t->mt_datpath); + /* What else to do?? Read/write gubbish... */ + vmtseterr(t); + return -1; + } + } + + len = VMT_TPS_COUNT(header); + if (len & VMT_TPS_ERRF) { + /* Error - for now, skip over it anyway */ + len &= MASK31; + } + if (len == 0) { + t->mt_eof = TRUE; /* Indicate hit tapemark */ + /* Must still back up over header again, drop thru */ + ioptr = -sizeof(header); + } else { + /* Skip back over data, position at start of record header. + */ + if ((len & 01) && (t->mt_format == VMT_FMT_TPS)) + len++; /* TPS pads up, VMT_FMT_TPE doesn't */ + ioptr = -(len + (sizeof(header)*2)); + } + (void) fseek(t->mt_datf, ioptr, SEEK_CUR); + + if (len == 0) /* If tapemark, don't bother checking header */ + return 1; + + /* Paranoia -- verify header matches trailer + */ + if ((res = fread(header, 1, sizeof(header), t->mt_datf)) + != sizeof(header)) { + vmterror(t, "rsp partial header: %d, data file \"%.256s\"", + res, + t->mt_datpath); + /* What else to do?? Read/write gubbish... */ + vmtseterr(t); + return -1; + } + hlen = VMT_TPS_COUNT(header); + if (hlen & VMT_TPS_ERRF) + hlen &= MASK31; + if (len != hlen) { + vmterror(t, "rsp header-trailer mismatch: %0lX vs %0lX for \"%.256s\"", + (long)hlen, (long)len, t->mt_datpath); + vmtseterr(t); + return -1; + } + (void) fseek(t->mt_datf, -sizeof(header), SEEK_CUR); + + + return 1; +} + +/* ================================================= + TPC format positioning + */ + +/* Space 1 record forward. +** Returns 1 on success, 0 failure, -1 error. +*/ +static int +tpc_recfwd(register struct vmtape *t) +{ + long ioptr; + int res; + uint32 len; + unsigned char header[2]; + + /* Ensure set up for reading */ + if (t->mt_state != TS_RDATA) + vmt_iobeg(t, FALSE); + + if ((res = fread(header, 1, sizeof(header), t->mt_datf)) + != sizeof(header)) { + if (feof(t->mt_datf)) { /* If at EOT, ignore even partial header */ + t->mt_eot = TRUE; + return 0; /* Fail */ + } else { + vmterror(t, "rsp partial header: %d, data file \"%.256s\"", + res, + t->mt_filename); + /* What else to do?? Read/write gubbish... */ + vmtseterr(t); + return -1; + } + } + len = (header[1]<<8) | header[0]; + if (len == 0) { + t->mt_eof = TRUE; /* Indicate hit tapemark */ + return 1; /* and succeed */ + } + + /* Skip over data. Don't bother checking post-data header, to allow + moving over possible bad stuff. Also ignore fseek error, next + i/o attempt will catch it. + */ + ioptr = len + (len&01); + if (t->mt_ispipe) + return vmtflushinp(t, ioptr) ? 1 : -1; + else { + if (fseek(t->mt_datf, ioptr, SEEK_CUR) != 0) { + vmterror(t, "rsp seek error: %d, data file \"%.256s\"", + errno, + t->mt_filename); + /* What else to do?? Read/write gubbish... */ + vmtseterr(t); + return -1; + } + } + return 1; +} + +/* Tape I/O - Start, end, EOF, EOT */ + +void +vmt_iobeg(register struct vmtape *t, + int wrtf) /* TRUE if about to write */ +{ + register struct vmttdrdef *td; + long ioptr; + + /* Caller should always check for writability first, but do a sanity + check anyway to catch buggy code. + */ + if (wrtf && (!vmt_iswritable(t) + || !(vmtfmttab[t->mt_format].tf_flags & VMTFF_WR))) { + vmterror(t, "write attempted to non-writable tape"); + /* Later return false value? */ + vmtseterr(t); + return; + } + + t->mt_frames = 0; /* Updated by any I/O */ + t->mt_err = 0; + t->mt_eof = t->mt_eot = t->mt_bot = FALSE; + t->mt_iowrt = wrtf; + + switch (t->mt_format) { + default: + break; + + case VMT_FMT_TPC: + if (t->mt_state != TS_RDATA) { /* Paranoia check */ + vmtseterr(t); + } + break; + + case VMT_FMT_TPE: + case VMT_FMT_TPS: + if ((wrtf && (t->mt_state != TS_RWRITE)) + || (!wrtf && (t->mt_state != TS_RDATA))) { + /* Must do a seek because we're changing read/write mode. + (required by ANSI C). Can skip this if already in right mode. + */ + ioptr = 0; + if (fseek(t->mt_datf, ioptr, SEEK_CUR)) { + vmterror(t, "%s fseek failed: data file \"%.256s\"", + (wrtf ? "write" : "read"), t->mt_datpath); + /* What else to do?? Read/write gubbish... */ + vmtseterr(t); + break; + } + t->mt_state = (wrtf ? TS_RWRITE : TS_RDATA); + } + break; + + case VMT_FMT_RAW: + rawposfix(t); /* Canonicalize read position */ + td = &t->mt_tdr; + if (wrtf) { + /* Caller must already have checked vmt_iswritable() to make sure + ** that writes are OK. + */ + t->mt_state = TS_RWRITE; + + if (td->tdcur) /* If not at EOF, */ + td_trunc(td); /* Truncate tape at this location! */ + + /* Now find data file IO pointer value to use, and position it. + ** May already be there, but must do it explicitly in case of + ** intervening tape reads or position commands. + */ + if (td->tdrd == NULL) + ioptr = 0; + else { + /* Get pointer to last record def in list */ + register struct vmtrecdef *rd = td->tdrd->rdprev; + ioptr = rd->rdloc + (rd->rdlen * rd->rdcnt); + } + td->tdbytes = ioptr; /* Remember highest output so far */ + + } else { + + /* If data is forthcoming, find data file IO pointer to use. */ + if (t->mt_state != TS_RDATA) + return; /* No data, nothing to do now */ + + /* Aha, set up */ + ioptr = td->tdcur->rdloc + (td->tdcur->rdlen * td->tdrncur); + td->tdrfcnt = td->tdcur->rdlen; + } + + /* Do seek on raw tape data file. + ** May already be positioned there, but must do it explicitly in case + ** of intervening tape reads or position commands. + ** (This is required by ANSI C) + */ + if (t->mt_ispipe) { + /* Do nothing and hope we're still in the right place! */ + } else if (fseek(t->mt_datf, ioptr, SEEK_SET)) { + vmterror(t, "%s fseek failed: data file \"%.256s\", loc %ld", + (wrtf ? "write" : "read"), + t->mt_datpath, (long)ioptr); + /* What else to do?? Read/write gubbish... */ + vmtseterr(t); + } + break; + } +} + +int +vmt_ioend(register struct vmtape *t) +{ + switch (t->mt_format) { + default: + return TRUE; + + case VMT_FMT_TPE: + case VMT_FMT_TPS: /* No cleanup necessary */ + break; + + case VMT_FMT_RAW: /* Handle raw tape */ + /* Check to see if any I/O actually happened (record data or tapemark). + ** It's possible for some errors to prevent I/O from happening, thus + ** the logical position hasn't changed. + */ + if (!t->mt_frames && !t->mt_eof) + return TRUE; + + if (t->mt_iowrt) { /* If writing, */ + if (t->mt_frames) { /* force out any data */ + fflush(t->mt_datf); + if (ferror(t->mt_datf)) + return FALSE; + if (!td_recapp(&t->mt_tdr, /* append new record def */ + t->mt_frames, 1, 0)) { + vmterror(t, "vmt_ioend: td_recapp malloc failed"); + return FALSE; + } + } + } else { /* If reading, */ + t->mt_tdr.tdrncur++; /* move past this record/tapemark */ + } + break; + } + return TRUE; +} + +/* Write EOF (tapemark, filemark) +** Caller has already checked for ability to write. +** Returns FALSE if couldn't do it. +*/ +int +vmt_eof(register struct vmtape *t) +{ + if (!vmt_iswritable(t)) { + /* Read-only tape or format, cannot write */ + t->mt_err++; + return FALSE; + } + vmt_iobeg(t, TRUE); /* Set up for write */ + + switch (t->mt_format) { + default: + break; /* Wrong format */ + + case VMT_FMT_TPE: + case VMT_FMT_TPS: + /* Just write zero length "record" */ + return vmt_rput(t, (unsigned char *)NULL, (size_t)0); + + case VMT_FMT_RAW: + /* Instead of calling vmt_ioend(), invoke a slightly different + ** operation and leave it at that. + ** Failure return means malloc failed. + */ + if (!td_recapp(&t->mt_tdr, (long)0, 0, 0)) { /* Append EOF */ + vmterror(t, "vmt_eof: td_recapp malloc failed"); + return FALSE; + } + return TRUE; + } + return FALSE; +} + +/* Write what amounts to EOT, when tape being unmounted or rewound. +** Finalize output data, write tape directory file. +*/ +int +vmt_eot(register struct vmtape *t) +{ /* But don't close file yet, let caller decide */ + int errs = 0; + + if (!vmt_iswritable(t)) { + /* Read-only tape or format, cannot write */ + t->mt_err++; + return FALSE; + } + + if (t->mt_datf) { + if ((fflush(t->mt_datf) != 0) /* Force out any remaining raw data */ + || ferror(t->mt_datf)) + errs++; + } + if (t->mt_ctlf) { /* If control file open */ + /* Write out tape directory */ + rewind(t->mt_ctlf); /* Rewind in case not the first update */ + td_fout(&t->mt_tdr, t->mt_ctlf, t->mt_datpath); + if ((fflush(t->mt_ctlf) != 0) + || ferror(t->mt_ctlf)) /* Check to see if any errors */ + errs++; + } + if (errs) t->mt_err = errs; + return (errs ? FALSE : TRUE); +} + + +/* Tape I/O - Actual data! */ + +#if VMTAPE_ITSDUMP + +/* ITS_WGET(t, wp) +** Returns TRUE if a word was read, FALSE otherwise. +** For a FALSE return, mt_err must be checked to determine +** whether the failure was due to an error. +*/ + +int +its_wget(register struct vmtape *t, + register w10_t *wp) +{ + switch (t->mt_state) { + case TS_THEAD: + if (--(t->mt_cnt) >= 0) { + *wp = *(t->mt_ptr)++; + t->mt_frames += VMT_ITS_FPW; + return TRUE; + } + t->mt_state = TS_FNEXT; + return its_wget(t, wp); + + case TS_FHEAD: + if (--(t->mt_cnt) >= 0) { + *wp = *(t->mt_ptr)++; + t->mt_frames += VMT_ITS_FPW; + return TRUE; + } + if (t->mt_fcur->fdfmt == FDFMT_LINK) { + t->mt_state = TS_FEOF; /* When done with link block, no more */ + return t->mt_frames /* If anything already read, */ + ? FALSE /* return end of record, */ + : its_wget(t, wp); /* else return tapemark (EOF) */ + } + t->mt_state = TS_FDATA; + /* Fall through to return data */ + + case TS_FDATA: + switch (wf_get(&t->mt_wf, wp)) { + case 1: + t->mt_frames += VMT_ITS_FPW; + return TRUE; + case 0: break; + case -1: t->mt_err++; + } + datf_close(t); + t->mt_state = TS_FEOF; /* No more data, so return EOF next time */ + return (t->mt_frames || t->mt_err) /* If anything already read, */ + ? FALSE /* return end of record, */ + : its_wget(t, wp); /* else return tapemark (EOF) */ + + case TS_FNEXT: + { + register struct vmtfildef *fd; + + if (!(fd = fd_nextget(t))) /* If no more files, */ + return FALSE; /* at EOT, state has been set */ + + if (fd->fdfmt == FDFMT_WFT) { + char fnbuf[400]; + if (os_fullpath(fnbuf, t->mt_filename, fd->fdfname, sizeof(fnbuf)) + > sizeof(fnbuf)) { + vmterror(t, "its_wget: path too big for %.128s & %.128s", + t->mt_filename, fd->fdfname); + return its_wget(t, wp); /* Try next one */ + } + + if (!datf_open(t, fnbuf, "zrb")) { + vmterror(t, "its_wget: Can't open: %.256s", fnbuf); + return its_wget(t, wp); /* Try next one */ + } + wf_init(&t->mt_wf, fd->fdwft, t->mt_datf); + + /* Put together a file block */ + LRHSET(t->mt_wbuf[TH_LKF], 0, 0); /* Say a file */ + t->mt_cnt = TH_FLN+1; + + /* Set creation time from source file, if not already set */ + if (!LHGET(fd->fdits.crdatim) && !RHGET(fd->fdits.crdatim)) + fd->fdits.crdatim = itsdate(t->mt_datf); + + } else if (fd->fdfmt == FDFMT_LINK) { + /* Put together a link block */ + + /* Set creation time from tape dir file, if not already set */ + if (!LHGET(fd->fdits.crdatim) && !RHGET(fd->fdits.crdatim)) + fd->fdits.crdatim = t->mt_fitsdate; + + LRHSET(t->mt_wbuf[TH_LKF], 1, 0); /* Say a link */ + + t->mt_wbuf[TH_LFN1] = fd->fdits.xfn1; /* Set file data */ + t->mt_wbuf[TH_LFN2] = fd->fdits.xfn2; + t->mt_wbuf[TH_LDIR] = fd->fdits.xdir; + t->mt_cnt = TH_LDIR+1; + } else { + vmterror(t, "its_wget: Bad file type %d.", fd->fdfmt); + return its_wget(t, wp); + } + + /* Common to all */ + LRHSET(t->mt_wbuf[TH_CNT], (-(TH_FLN+1))&H10MASK, 0); + t->mt_wbuf[TH_DIR] = fd->fdits.dir; + t->mt_wbuf[TH_FN1] = fd->fdits.fn1; + t->mt_wbuf[TH_FN2] = fd->fdits.fn2; + t->mt_wbuf[TH_CRD] = fd->fdits.crdatim; + if (LHGET(fd->fdits.rfdatetc) || RHGET(fd->fdits.rfdatetc)) + t->mt_wbuf[TH_RFD] = fd->fdits.rfdatetc; + else /* Unknown info */ + LRHSET(t->mt_wbuf[TH_RFD], H10MASK, 0777000); + LRHSET(t->mt_wbuf[TH_FLN], H10MASK, H10MASK); /* Unknown len */ + t->mt_ptr = t->mt_wbuf; + t->mt_state = TS_FHEAD; + return its_wget(t, wp); + } + + case TS_FEOF: + t->mt_eof = TRUE; + t->mt_state = TS_FNEXT; + return FALSE; + + case TS_EOT: + t->mt_eot = TRUE; + return FALSE; + + case TS_ERR: + case TS_CLOSED: + default: + t->mt_err++; + return FALSE; + } +} + +#endif /* VMTAPE_ITSDUMP */ + +/* Do I/O on complete records (raw frame bytes) */ + +/* VMT_RGET - Read a raw record. +** Returns TRUE if some data was read; FALSE if not. +** FALSE could mean either an EOF, or EOT, or error; data structure +** must be checked with vmt_*() macros. +*/ +int +vmt_rget(register struct vmtape *t, + register unsigned char *buff, + size_t len) +{ + register size_t nget; + int res = FALSE; + + vmt_iobeg(t, FALSE); /* Set up for reading */ + + switch (t->mt_format) { + default: + t->mt_err++; + return FALSE; + +#if VMTAPE_ITSDUMP + case VMT_FMT_ITS: + { + w10_t w; + register unsigned char *ucp = buff; + register int wlen; + + /* ITSDUMP source input has no record length boundaries, so each + file could be a single huge record. In tne interest of sanity, + break things up into a manageable 1K words that corresponds to + the normal buffer size used by DUMP (or ITS). + If the caller wants a smaller record, just do that, with the + proviso that we will never return part of a word. + */ + if (len > VMT_ITS_DEFRECLEN) + len = VMT_ITS_DEFRECLEN; + wlen = len / VMT_ITS_FPW; + if (wlen == 0) { /* Buffer too small? */ + vmterror(t, "Unsupportable buffer size: %ld", (long)len); + t->mt_err++; + return FALSE; + } + for (; wlen > 0; --wlen, t->mt_frames += VMT_ITS_FPW) { + if (!its_wget(t, &w)) { + if (t->mt_frames) + return TRUE; + /* Tapemark, BOT/EOT, or some kind of error, just pass along */ + return FALSE; + } + /* Got word, convert to bytes and put in buffer. + Assumes core-dump mode; ITSDUMP meaningless otherwise. + */ + *ucp++ = (LHGET(w)>>10) & 0377; + *ucp++ = (LHGET(w)>> 2) & 0377; + *ucp++ = ((LHGET(w)&03)<<6) | ((RHGET(w)>>12)&077); + *ucp++ = (RHGET(w)>>4) & 0377; + *ucp++ = RHGET(w) & 017; + } + /* No need to call vmt_ioend() */ + return TRUE; + } +#endif /* VMTAPE_ITSDUMP */ + + case VMT_FMT_RAW: + switch (t->mt_state) { + case TS_EOT: + t->mt_eot = TRUE; + return FALSE; /* Don't call vmt_ioend */ + + case TS_REOF: + t->mt_eof = TRUE; + t->mt_state = TS_RDATA; + break; + + case TS_RDATA: + if (len > t->mt_tdr.tdrfcnt) { + len = t->mt_tdr.tdrfcnt; /* Truncate our request */ + if (len <= 0) + break; /* Nothing in record??? */ + } + + if ((nget = fread(buff, 1, len, t->mt_datf)) != len) { + /* Handle premature EOF on tape data file */ + vmterror(t, "Premature EOF on tape data file \"%.256s\" (read %ld of %ld)", + t->mt_datpath, + (long)nget, (long)len); + len = nget; + t->mt_eot = TRUE; + t->mt_state = TS_EOT; + } + t->mt_frames += nget; + t->mt_tdr.tdrfcnt -= nget; + res = TRUE; + break; + + default: /* Bad state?? */ + /* Should return error indicator, but also try to get back into + a good state. + */ + t->mt_err++; + t->mt_state = TS_RDATA; /* Wild guess */ + res = FALSE; + break; + } + + (void) vmt_ioend(t); + break; + + case VMT_FMT_TPE: + case VMT_FMT_TPS: + { + long ioptr; + uint32 reclen; + unsigned char header[4]; + unsigned char trailer[8]; + + if (t->mt_state != TS_RDATA) { + /* Bad state?? */ + t->mt_err++; + t->mt_state = TS_RDATA; + return FALSE; + } + if ((res = fread(header, 1, sizeof(header), t->mt_datf)) + != sizeof(header)) { + if (feof(t->mt_datf)) { /* If at EOT, ignore even partial header */ + t->mt_eot = TRUE; + return FALSE; /* Fail */ + } else { + vmterror(t, "rget partial header: %d, data file \"%.256s\"", + res, + t->mt_datpath); + /* What else to do?? Read/write gubbish... */ + vmtseterr(t); + return FALSE; + } + } + reclen = (header[3]<<24)|(header[2]<<16) | (header[1]<<8) | header[0]; + if (reclen & VMT_TPS_ERRF) { + /* Error - for now, go for it anyway */ + reclen &= MASK31; + } + if (reclen == 0) { + t->mt_eof = TRUE; /* Indicate hit tapemark */ + return FALSE; /* No data read */ + } + if (len > reclen) + len = reclen; /* Truncate our request */ + else if (reclen > len) { + /* Record length longer than our buffer. This should + never happen, but nothing stops someone from creating + a bogus tape. Satisfy request but complain. + */ + vmterror(t, "warning: reclen > buflen (%ld > %ld) for \"%.256s\"", + (long)reclen, (long)len, t->mt_datpath); + } + + /* Read data! */ + if ((nget = fread(buff, 1, len, t->mt_datf)) + != len) { + vmterror(t, "rget partial header: %ld, data file \"%.256s\"", + (long)nget, + t->mt_datpath); + /* What else to do?? Read/write gubbish... */ + vmtseterr(t); + return FALSE; + } + t->mt_frames += nget; + if (reclen > len) { + /* Skip over unread portion of record */ + if (t->mt_ispipe) + (void) vmtflushinp(t, (size_t)(reclen-len)); + else + (void) fseek(t->mt_datf, (long)(reclen-len), SEEK_CUR); + } + + /* Read trailer and verify it. + */ + memset(trailer, 0, sizeof(trailer)); + if ((reclen & 01) && (t->mt_format == VMT_FMT_TPE)) + reclen = 0; /* Pad unless VMT_FMT_TPE */ + (void) fread(trailer+4-(reclen&01), 1, 4+(reclen&01), t->mt_datf); + if (memcmp(header, trailer+4, sizeof(header))) { + uint32 headv = (header[3]<<24) | (header[2]<<16) + | (header[1]<<8) | header[0]; + uint32 trailv = (trailer[7]<<24) | (trailer[6]<<16) + | (trailer[5]<<8) | trailer[4]; + vmterror(t, +"warning: header-trailer mismatch: %0lX vs %0lX for \"%.256s\"", + (long)headv, (long)trailv, + t->mt_datpath); + } + + (void) vmt_ioend(t); + break; + } + + case VMT_FMT_TPC: + { + long ioptr; + uint32 reclen; + unsigned char header[2]; + + if (t->mt_state != TS_RDATA) { + /* Bad state?? */ + t->mt_err++; + t->mt_state = TS_RDATA; + return FALSE; + } + if ((res = fread(header, 1, sizeof(header), t->mt_datf)) + != sizeof(header)) { + if (feof(t->mt_datf)) { /* If at EOT, ignore even partial header */ + t->mt_eot = TRUE; + return FALSE; /* Fail */ + } else { + vmterror(t, "rget partial header: %d, data file \"%.256s\"", + res, + t->mt_datpath); + /* What else to do?? Read/write gubbish... */ + vmtseterr(t); + return FALSE; + } + } + reclen = (header[1]<<8) | header[0]; + if (reclen == 0) { + t->mt_eof = TRUE; /* Indicate hit tapemark */ + return FALSE; /* No data read */ + } + if (len > reclen) + len = reclen; /* Truncate our request */ + else if (reclen > len) { + /* Record length longer than our buffer. This should + never happen, but nothing stops someone from creating + a bogus tape. Satisfy request but complain. + */ + vmterror(t, "warning: reclen > buflen (%ld > %ld) for \"%.256s\"", + (long)reclen, (long)len, t->mt_datpath); + } + + /* Read data! */ + if ((nget = fread(buff, 1, len, t->mt_datf)) + != len) { + vmterror(t, "rget partial header: %ld, data file \"%.256s\"", + (long)nget, + t->mt_datpath); + /* What else to do?? Read/write gubbish... */ + vmtseterr(t); + return FALSE; + } + t->mt_frames += nget; + if (reclen > len) { + /* Skip over unread portion of record */ + if (t->mt_ispipe) + (void) vmtflushinp(t, (size_t)(reclen-len)); + else + (void) fseek(t->mt_datf, (long)(reclen-len), SEEK_CUR); + } + /* TPC has no trailer, but may align up?? */ + if (reclen & 01) + (void) fgetc(t->mt_datf); + + (void) vmt_ioend(t); + break; + } + + } + return res; +} + + +/* Write a record +*/ +int +vmt_rput(register struct vmtape *t, + register unsigned char *buff, + size_t len) +{ + size_t nput; + + if (!(t->mt_fmtp->tf_flags & VMTFF_WR)) { + /* Read-only format, cannot write */ + t->mt_err++; + return FALSE; + } + vmt_iobeg(t, TRUE); + + if (t->mt_state != TS_RWRITE) { + t->mt_err++; + return FALSE; + } + + switch (t->mt_format) { + int pad; + unsigned char header[8]; /* For best alignment */ + + case VMT_FMT_TPS: + if (pad = (len & 01)) { /* Must pad? */ + header[3] = 0; + } else { + case VMT_FMT_TPE: + pad = 0; + } + header[4] = len & 0377; + header[5] = (len >> 8) & 0377; + header[6] = (len >> 16) & 0377; + header[7] = (len >> 24) & 0377; + + if ((fwrite(header+4, 1, 4, t->mt_datf) == -1) + || (len && ((nput = fwrite(buff, 1, len, t->mt_datf)) == -1)) + || (len && (fwrite(header+4-pad, 1, 4+pad, t->mt_datf)) == -1) ) { + vmterror(t, "Output error on tape data file \"%.256s\"", + t->mt_datpath); + t->mt_err++; + return FALSE; + } + t->mt_frames = nput; + break; + + case VMT_FMT_RAW: + nput = fwrite(buff, 1, len, t->mt_datf); + if (nput != -1) + t->mt_frames = nput; + else { + vmterror(t, "Output error on tape data file \"%.256s\"", + t->mt_datpath); + t->mt_err++; + return FALSE; + } + break; + } + return vmt_ioend(t); +} + +/* Add error indication onto last record. +** Only intended for use by tdcopy program. +** If uselast is TRUE, uses last record (right after a vmt_ioend!) +** otherwise creates a new, null record to hold the error. +*/ +int +vmt_eput(register struct vmtape *t, + int err, int uselast) +{ + switch (t->mt_format) { + case VMT_FMT_RAW: + if (!uselast || !t->mt_tdr.tdrd || !t->mt_tdr.tdcur) { + vmt_iobeg(t, TRUE); /* Set up for write */ + if (!td_recapp(&t->mt_tdr, (long)0, 1, err)) { + vmterror(t, "vmt_eput: td_recapp malloc failed"); + return FALSE; + } + return TRUE; + } + t->mt_tdr.tdcur->rderr = err; + break; + case VMT_FMT_TPE: + case VMT_FMT_TPS: + /* Possible but not implemented yet */ + break; + } + return TRUE; +} + +/* + Description of Control Tape file format + (formerly "Tape Directory format") + + A tape directory is a text file that describes the structure of +the associated tape data file. The data file is simply a continuous +stream of 8-bit bytes corresponding to the logical stream of 8-bit tape data +frames. + The directory is formatted as a set of logical text lines. +Each line begins with a keyword optionally followed by data. A +logical line may be continued over several physical lines. Whitespace +is ignored. Comments are introduced by the character ';' and continue +to the end of that physical line. + +Keywords: + TF-Format: [] + ; Tape datafile format and optional filename + <#>: ; File/Record descriptors + ; optional continuation of above + EOT: ; Phys End of Tape, remaining text ignored. + + - describes how the tape file data is stored on disk, one of: + "raw" - 8-bit bytes, one per frame. (T20: bytesize 8) + "packed" - T20 only. + + - name of tape data file. If none, name is built using + ".tap" as an extension to the name of the tape directory file. + This extension replaces ".tdr" if it exists, otherwise ".tap" is + simply appended. + +<#> - location in tapefile of 1st record beginning next file. For "raw" + format this corresponds to the logical frame # on tape as well, but + for other formats this may serve a realignment purpose. + The string "BOT" is considered equivalent to "0". + + - a list of record descriptors composing the file, + separated by whitespace. The list may be empty, as is normally + the case for two consecutive tapemarks that signal a logical EOT. + + - A record descriptor, with the format + [*][E[]] + where: + - decimal record length, in frames (8-bit bytes) + - # times this record length recurs. + E - indicates error when reading this record (or the last of a + series). is an optional # and specifies additional + information, if any, as to nature of the error. + + The string "EOF" is a special that signals a tape mark. + +NOTE: may want to make EOF a special instead, mainly so an error +indication can be bound to it. +*/ + +/* Grovel over tape directory -- a bunch of lines, each line of format: +** +** ITSFILE: dir fn1 fn2 +** is one of: +** "->" dir fn1 fn2 ; an ITS link +** filename ; a data file of 36-bit words, +** ; in format indicated by , +** ; anything that wfio.h knows about. +** ; Usually "u36". +** +** ITSDIR: dir +** where dirpathname is a directory containing u36-type files, +** all of which are read in. Later, may interpret DIR.LIST too. +*/ + +static int fileread(FILE *f, char **acp, size_t *asz); +static char *wdscan(char **acp); +static int numscan(char *cp, char **acp, long int *aval); +static void filscan(struct vmtape *t, struct vmttdrdef *td, char *cp); +#if VMTAPE_ITSDUMP +static struct vmtfildef *itsparse(struct vmtape *t, char **acp); +#endif + +#define TDRERR(t,args) ((t)->mt_ntdrerrs++, tdrwarn args, 0) + +static void +tdr_reset(struct vmtape *t) +{ + td_reset(&t->mt_tdr); /* Flush raw tape stuff */ + +#if VMTAPE_ITSDUMP + if (t->mt_fdefs) { /* Flush non-raw FDF stuff */ + register struct vmtfildef *fd = t->mt_fdefs, *next; + do { + next = fd->fdnext; + free((char *)fd); + } while ((fd = next) != t->mt_fdefs); + t->mt_fdefs = NULL; + } + t->mt_fcur = NULL; +#endif /* VMTAPE_ITSDUMP */ + + if (t->mt_contents) { /* Flush tapefile buffer */ + free(t->mt_contents); + t->mt_contents = NULL; + } +} + +static int +tdr_scan(struct vmtape *t, + FILE *f, + char *fname) +{ + struct vmttdrdef *td = &t->mt_tdr; + int indent; + char *cp; + char *key, *num; + long tloc = 0; + size_t fsize = 0; + char *line, *nline; + char *savetpath = t->mt_filename; /* Save & restore this */ + + t->mt_filename = fname; /* Use this for TDRERR msgs */ + + tdr_reset(t); + + t->mt_ntdrerrs = 0; + t->mt_format = 0; + t->mt_fmtp = &vmtfmttab[t->mt_format]; + + /* Set up initial file to read */ + if (fileread(f, &(t->mt_contents), &fsize) <= 0) { + TDRERR(t, (t,"Tape-file readin failed: %ld bytes", fsize)); + } else + for (nline = t->mt_contents; cp = nline; t->mt_lineno++) { + /* Trim off a line */ + if (nline = strchr(nline, '\n')) + *nline++ = '\0'; /* Truncate line, point past EOL */ + line = cp; /* Remember start of line */ + + /* Parse line */ + indent = (*cp == ' ') || (*cp == '\t'); /* Remember if indentation */ + while (isspace(*cp)) ++cp; /* Flush initial whitespace */ + if (key = strchr(cp, ';')) /* Strip off comments here */ + *key = '\0'; + if (!*cp) continue; /* Ignore blank lines */ + + /* If indented, assume it's a continuation of a raw record format + ** specification + */ + if (indent) { + if (!td->tdrd) { + TDRERR(t,(t, "Indented record defs without active tape/file def")); + continue; + } + filscan(t, td, cp); + continue; + } + + /* Not indented, look for regular keyword of form "foo:" */ + if (!(cp = strpbrk(key = cp, ": \t")) || *cp != ':') { + TDRERR(t,(t, "No keyword: \"%s\"", key)); + continue; + } + *cp++ = 0; /* Zap colon, move over */ + if (numscan(key, &num, &tloc) && !*num) { /* All digits? */ + /* Assume it's a record definition */ + if (tloc == 0 && td->tdrd) { + TDRERR(t,(t, "Record descs seen before BOT")); + } else if (tloc != td->tdbytes) { + if (tloc < td->tdbytes) + TDRERR(t,(t, "Tape overlap? Loc now %ld, TD file says %ld", + tloc, td->tdbytes)); + else /* Warning, not true error */ + tdrwarn(t, "Tape gap? Loc now %ld, TD file says %ld", + tloc, td->tdbytes); + } + td->tdbytes = tloc; /* Set file location as given */ + filscan(t, td, cp); /* Scan line and link recs into list */ + + } else if (strcasecmp(key, "BOT")==0) { + if (td->tdrd || td->tdbytes) { + TDRERR(t,(t, "BOT already seen")); + continue; + } + filscan(t, td, cp); /* Scan line and link recs into list */ + + } else if (strcasecmp(key, "EOT")==0) { + + } else if (strcasecmp(key, "TF-Format")==0) { + char *fmtstr; + + if (t->mt_format) { + TDRERR(t,(t, "TF-Format already seen")); + continue; + } + if (!(t->mt_format = vmt_strtofmt(fmtstr = wdscan(&cp)))) { + TDRERR(t,(t,"Unknown TF-Format: %s", fmtstr)); + continue; + } + t->mt_fmtp = &vmtfmttab[t->mt_format]; + cp = wdscan(&cp); /* Get optional data filename */ + if (cp) { + if (!(td->tdfname = dupcstr(cp))) { + TDRERR(t,(t, "malloc failed for tdfname")); + continue; + } + } else + td->tdfname = NULL; +#if VMTAPE_ITSDUMP + } else if (strcasecmp(key, "ITSFILE")==0) { + struct vmtfildef *fd; + + if (t->mt_format == 0) { + t->mt_format = VMT_FMT_ITS; + t->mt_fmtp = &vmtfmttab[t->mt_format]; + } + fd = itsparse(t, &cp); + if (fd) { + /* Link in to circular list */ + if (t->mt_fdefs) { + fd->fdnext = t->mt_fdefs; + fd->fdprev = t->mt_fdefs->fdprev; + fd->fdprev->fdnext = fd; + t->mt_fdefs->fdprev = fd; + } else { + t->mt_fdefs = fd->fdnext = fd->fdprev = fd; + } + } + continue; + } else if (strcasecmp(key, "ITSDIR")==0) { + /* Insert hack code here */ +#endif /* VMTAPE_ITSDUMP */ + + + } else + TDRERR(t,(t, "Unknown keyword \"%s\"", key)); + } + + if (!feof(f)) + TDRERR(t,(t, "Input I/O error")); + + t->mt_filename = savetpath; /* Restore saved tapefile path */ + if (t->mt_ntdrerrs) { + /* Scan failed, clean up */ + tdr_reset(t); + t->mt_format = 0; + t->mt_fmtp = &vmtfmttab[t->mt_format]; + return FALSE; + } + return TRUE; +} + + +static int +numscan(char *cp, char **acp, long *aval) +{ + register char *s = cp; + long val = 0; + while (isdigit(*s)) { + val = (val * 10) + (*s - '0'); + ++s; + } + *aval = val; + *acp = s; + return (s == cp) ? 0 : 1; +} + + + +/* Read in file into a single memory chunk */ +static int +fileread(FILE *f, + char **acp, + size_t *asz) +{ + register size_t size = 0; + register char *ptr = NULL, *cp, *op; + register int res; + +# define CHUNKSIZE 1024 + for (;;) { + ptr = (op = ptr) ? realloc(ptr, size+CHUNKSIZE+1) + : malloc(CHUNKSIZE+1); + if (!ptr) { + *acp = "Out of memory for file"; + if (op) + free(op); + return 0; + } + cp = ptr + size; + res = fread(cp, 1, CHUNKSIZE, f); + if (res > 0) + size += res; + if (res < CHUNKSIZE) { + if (!feof(f) || ferror(f)) { + free(ptr); + *acp = "Error during file input"; + return -1; + } + if (!(ptr = realloc((op = ptr), size+1))) { + free(op); + *acp = "File memory trim error"; + return 0; + } + ptr[size] = '\0'; /* Ensure nul-terminated just in case */ + *acp = ptr; + *asz = size; + return 1; + } + } +} + +/* Scan a list of records constituting a tape file. +*/ + +static void +filscan(struct vmtape *t, + struct vmttdrdef *td, + char *cp) +{ + char *rs; + long len, cnt, err; + + while (rs = wdscan(&cp)) { + char *cs, *es; + + if (!numscan(rs, &cs, &len)) { + if (strcasecmp(rs, "EOF") == 0) { /* Tapemark? */ + if (td_recapp(td, (long)0, 0, 0) == NULL) /* Yep, add one here */ + TDRERR(t,(t, "malloc failed while adding tapemark")); + } else + TDRERR(t,(t, "Bad reclen syntax")); + continue; + } + if (*cs == '*') { + if (!numscan(++cs, &cs, &cnt)) { + TDRERR(t,(t, "Bad reccnt syntax")); + continue; + } + } else cnt = 1; + if (*cs) { + if (*cs != 'e' || *cs != 'E') { + TDRERR(t,(t, "Bad record syntax")); + continue; + } + if (!numscan(++cs, &es, &err) || *es) { + TDRERR(t,(t, "Bad recerr syntax")); + continue; + } + } else err = 0; + + /* Add record to list */ + if (td_recapp(td, len, (int)cnt, (int)err) == NULL) + TDRERR(t,(t, "malloc failed while adding record")); + } +} + + +static char * +wdscan(char **acp) +{ + register char *cp = *acp, *s; + + if (isspace(*cp)) /* Skip to word */ + while (isspace(*++cp)); + s = cp; + switch (*s) { + case 0: return NULL; /* Nothing left */ + default: /* Normal word */ + while (!isspace(*++s) && *s); + if (*s) *s++ = 0; /* Tie off word */ + break; + } + *acp = s; + return cp; +} + +static char * +dupcstr(char *s) +{ + char *cp; + + cp = malloc(strlen(s)+1); + if (!cp) + return NULL; + return strcpy(cp, s); +} + +/* ITSDUMP tape directory parsing and misc auxiliaries */ + +#if VMTAPE_ITSDUMP + +static struct vmtfildef * +itsparse(struct vmtape *t, char **acp) +{ + struct vmtfildef *fd; + int islink; + int wft; + char *dir, *fn1, *fn2, *type, *xdir, *xfn1, *xfn2; + char *screat, *sref, *sbsz, *slen, *sauth; + long creat, ref, bsz, len; + + dir = wdscan(acp); + fn1 = wdscan(acp); + fn2 = wdscan(acp); + type = wdscan(acp); + + screat = sref = sbsz = slen = sauth = NULL; + creat = ref = bsz = len = 0; + if (type && !strcmp(type, "{")) { /* Special extra file info? */ + for (;;) { /* Fake loop so break can work */ + if ((type = wdscan(acp)) + && strcmp(type, "}")) screat = type; else break; + if ((type = wdscan(acp)) + && strcmp(type, "}")) sref = type; else break; + if ((type = wdscan(acp)) + && strcmp(type, "}")) sbsz = type; else break; + if ((type = wdscan(acp)) + && strcmp(type, "}")) slen = type; else break; + if ((type = wdscan(acp)) + && strcmp(type, "}")) sauth = type; else break; + type = wdscan(acp); + break; + } + if (!type || strcmp(type, "}")) { + TDRERR(t,(t, "Bad ITSfile spec, missing close-brace")); + return NULL; + } + type = wdscan(acp); /* OK, next thing should really be type */ + } + xdir = wdscan(acp); + xfn1 = wdscan(acp); + xfn2 = wdscan(acp); + if (!type || !xdir) { + TDRERR(t,(t, "Bad ITSfile spec")); + return NULL; + } + + /* Check out file type */ + if (!strcmp(type, "->")) islink = TRUE; + else { + islink = FALSE; + if ((wft = wf_type(type)) < 0) { + TDRERR(t,(t, "Unknown ITSfile format: \"%s\"", type)); + return NULL; + } + } + + /* Check out any numbers */ + if (screat && !sscanf(screat, "%li", &creat)) { + TDRERR(t,(t, "Bad ITSfile creation date: \"%s\"", screat)); + return NULL; + } + if (sref && !sscanf(sref, "%li", &ref)) { + TDRERR(t,(t, "Bad ITSfile reference date: \"%s\"", sref)); + return NULL; + } + if (sbsz && !sscanf(sbsz, "%li", &bsz)) { + TDRERR(t,(t, "Bad ITSfile bytesize: \"%s\"", sbsz)); + return NULL; + } + if (slen && !sscanf(slen, "%li", &len)) { + TDRERR(t,(t, "Bad ITSfile length: \"%s\"", slen)); + return NULL; + } + + /* Allocate structure for this ITSfile spec. Note all data is + ** automatically cleared, so stuff like crdatim needn't be set. + */ + if (!(fd = (struct vmtfildef *)calloc(1, sizeof(*fd)))) { + TDRERR(t,(t, "Out of memory for filespec")); + return NULL; + } + fd->fdnext = NULL; + + fd->fdits.dir = sixbit(dir); + fd->fdits.fn1 = sixbit(fn1); + fd->fdits.fn2 = sixbit(fn2); + if (islink) { + fd->fdfmt = FDFMT_LINK; + fd->fdits.linkf = 1; + fd->fdfname = NULL; + fd->fdits.xdir = sixbit(xdir); + fd->fdits.xfn1 = sixbit(xfn1); + fd->fdits.xfn2 = sixbit(xfn2); + } else { + fd->fdfmt = FDFMT_WFT; + fd->fdwft = wft; + fd->fdits.linkf = 0; + fd->fdfname = xdir; + } + + /* Now do extra info, which has peculiar formats */ + if (screat) + fd->fdits.crdatim = itsnet2qdat(creat); + if (sref) + fd->fdits.rfdatetc = itsnet2qdat(ref); + if (slen) + fd->fdits.wdlen = itsfillen(bsz, len, &(fd->fdits.rfdatetc)); + if (sauth) + fd->fdits.auth = sixbit(sauth); + + return fd; +} + +/* This assumes we're using ASCII character set! */ +#define tosixbit(c) ((((c) & 0100) ? (c)|040 : (c)&~040)&077) + +static w10_t +sixbit(register char *str) +{ + register int i = 6*6; + register unsigned c; + register h10_t lh = 0, rh = 0; + w10_t w; + + if (str) { + while (i > 0 && (c = *str++)) { + if (c == '~') /* Translation hack so spaces can be embedded */ + c = ' '; + if (i > 18) + lh |= (h10_t)tosixbit(c) << ((i -= 6)-18); + else + rh |= (h10_t)tosixbit(c) << (i -= 6); + } + } + LRHSET(w, lh, rh); + return w; +} + +#include /* For struct tm */ + +static w10_t +itsdate(FILE *f) +{ + register w10_t w; + struct tm tm; + + LRHSET(w, 0, 0); + if (os_fmtime(f, &tm)) { /* Get last modified time of open file */ + /* Now put together an ITS time word */ + LHSET(w, ((h10_t)(tm.tm_year % 100)<<9) | ((tm.tm_mon+1)<<5) | tm.tm_mday); + RHSET(w, ((((h10_t)tm.tm_hour * 60) + tm.tm_min)*60 + tm.tm_sec) << 1); + } + return w; +} + +/* Convert from Network 32-bit standard time (RFC 738) to ITS +** disk-format date/time +*/ + +static w10_t +itsnet2qdat(uint32 ntm) +{ + register w10_t w; + struct tm tm; + + LRHSET(w, 0, 0); + + /* Special hack - assume time is coming from a real ITS dump, and + ** compensate for timezone so dates don't change. ITS always assumed + ** EST/EDT (-5 hrs from GMT). In order for local time breakdown to + ** provide us with the original EST values, we need to add in the offset + ** between the local timezone and EST. + ** Yes, this is very site-dependent and should be an OS dependent routine. + ** Current assumption for testing/debugging is local zone of PST. + */ +#define NTM_ITSTZOFF ((8-5)*60*60) + ntm += NTM_ITSTZOFF; + + if (os_net2tm(ntm, &tm)) { /* Get time breakdown for Net time value */ + /* Now put together an ITS time word */ + LHSET(w, ((h10_t)(tm.tm_year % 100)<<9) | ((tm.tm_mon+1)<<5) | tm.tm_mday); + RHSET(w, ((((h10_t)tm.tm_hour * 60) + tm.tm_min)*60 + tm.tm_sec) << 1); + } + return w; +} + +/* Given bytesize and bytecount, find length in words and +** set funny bits in RH of reference date to indicate actual EOF. +*/ +static w10_t +itsfillen(register uint32 bsz, + register uint32 bcnt, + w10_t *rfd) +{ + register uint32 bpw, wlen; + register w10_t w; + + if (bsz == 0) { /* Leave refdate field 0, assume S=36 */ + LRHSET(w, bcnt >> H10BITS, bcnt & H10MASK); + return w; + } + if (bsz > W10BITS) + bsz = W10BITS; + bpw = W10BITS / bsz; /* Find # bytes per word */ + wlen = bcnt / bpw; /* Find # words */ + bcnt %= bpw; /* Find remainder */ + if (bcnt) + ++wlen; + LRHSET(w, wlen >> H10BITS, wlen & H10MASK); + + /* Now compute funny bits in RH of ref date. These are the low 9 bits, + ** the meanings of which are documented in SYSTEM;FSDEFS >. + */ +/* + ;LET S=BITS PER BYTE, C=COUNT OF UNUSED BYTES IN LAST WD + ;400+100xS+C S=1 TO 3 C=0 TO 35. + ;200+20xS+C S=4 TO 7 C=0 TO 8 + ;44+4xS+C S=8 TO 18. C=0 TO 3 + ;44-S S=19. TO 36. C=0 + ;NOTE THAT OLD FILES HAVE UNBYTE=0 => S=36. +*/ + bcnt = bpw - bcnt; /* Turn # bytes left into # bytes unused */ + if (bsz < 4) + bsz = 0400 + (0100 * bsz) + bcnt; + else if (bsz < 8) + bsz = 0200 + (020 * bsz) + bcnt; + else if (bsz < 19) + bsz = 044 + (04 * bsz) + bcnt; + else + bsz = 044 - bsz; + + RHSET(*rfd, RHGET(*rfd) | (bsz & 0777)); + return w; +} +#endif /* VMTAPE_ITSDUMP */ + +/* Tape Directory functions for raw magtape files */ + +static void td_rdadd(struct vmttdrdef *td, struct vmtrecdef *rd); +static void td_rddel(struct vmttdrdef *td, struct vmtrecdef *rd); + +static void +td_init(struct vmttdrdef *td) +{ + td->tdfmt = 0; + td->tdfils = td->tdrecs = td->tdbytes = 0; + td->tdrd = td->tdcur = NULL; + td->tdfname = NULL; + td->tdmaxrsiz = 0; +} + +static void +td_reset(register struct vmttdrdef *td) +{ + if (td->tdfname) + free(td->tdfname); + while (td->tdrd) + td_rddel(td, td->tdrd->rdprev); + td_init(td); +} + + +/* Output ASCII version of tape directory */ +static void +td_fout(struct vmttdrdef *td, + FILE *f, + char *fnam) +{ + struct vmtrecdef *rd; + int bol = TRUE; + + fprintf(f, "\ +; Tape directory for %s\n\ +; Bytes: %ld, Records: %d, Files(EOF marks): %d\n\ +TF-Format: raw %s\n", + fnam ? fnam : "", + td->tdbytes, td->tdrecs, td->tdfils, + fnam ? fnam : ""); + if (rd = td->tdrd) { + do { + if (bol) { + fprintf(f, "%lu:", rd->rdloc); + bol = FALSE; + } + if (rd->rdcnt == 0) { + fprintf(f, " EOF\n"); + bol = TRUE; + } else if (rd->rdcnt == 1) fprintf(f, " %lu", rd->rdlen); + else fprintf(f, " %lu*%u", rd->rdlen, rd->rdcnt); + } while ((rd = rd->rdnext) != td->tdrd); + } + if (!bol) fprintf(f, "\n"); + fprintf(f, "EOT:\n"); +} + +/* Append a record to end of tape directory. +** Count 0 means a tapemark (EOF). +** Returns NULL if needs to allocate new record def and out of space. +*/ + +static struct vmtrecdef * +td_recapp(register struct vmttdrdef *td, + unsigned long len, + int cnt, int err) +{ + register struct vmtrecdef *rd; + + /* Try for fast merge with previous record */ + if (td->tdrd) { + rd = td->tdrd->rdprev; /* Get prev record */ + if (!err && !rd->rderr && cnt && rd->rdcnt && len == rd->rdlen) { + rd->rdcnt += cnt; + td->tdrecs += cnt; + td->tdbytes += (len * cnt); + return rd; + } + } + + /* Can't merge, create new rec */ + if (!(rd = (struct vmtrecdef *)malloc(sizeof(struct vmtrecdef)))) { + /* Up to caller to report errors... */ + return NULL; + } + rd->rdloc = td->tdbytes; + rd->rdlen = len; + rd->rdcnt = cnt; + rd->rderr = err; + td_rdadd(td, rd); /* Add to circ list */ + if (cnt) { /* Normal data record */ + td->tdrecs += cnt; + td->tdbytes += (len * cnt); + if (td->tdmaxrsiz < len) /* Update max rec size seen */ + td->tdmaxrsiz = len; + } else { /* Tapemark (EOF) */ + td->tdfils++; + } + return rd; +} + +/* Link into overall list */ + +static void +td_rdadd(struct vmttdrdef *td, + struct vmtrecdef *rd) +{ + if (!td->tdrd) { + td->tdrd = rd->rdprev = rd->rdnext = rd; + return; + } + rd->rdnext = td->tdrd; + rd->rdprev = td->tdrd->rdprev; + rd->rdprev->rdnext = rd; + td->tdrd->rdprev = rd; +} + +/* Unlink from overall list */ + +static void +td_rddel(register struct vmttdrdef *td, + register struct vmtrecdef *rd) +{ + register struct vmtrecdef *rdn = rd->rdnext; + + if (rd == rdn) /* Only thing on list? */ + td->tdrd = NULL; + else { + if (rd == td->tdrd) /* Ugh, flushing start of multiple list?! */ + td->tdrd = rdn; /* Dumb solution, make next node be start */ + + rd->rdprev->rdnext = rdn; /* Link previous forward to next */ + rdn->rdprev = rd->rdprev; /* Link next back to previous */ + } + free((char *)rd); +} + +/* Truncate the list after the current canonicalized logical position. +** This is used when about to write to the tape. +*/ +static void +td_trunc(register struct vmttdrdef *td) +{ + register struct vmtrecdef *rd = td->tdcur; + + if (rd == NULL) /* If already at EOT */ + return; + + if (td->tdrncur > 0) { /* Pos is inside a vmtrecdef? */ + rd->rdcnt = td->tdrncur; /* Chop off remaining count */ + if ((rd = rd->rdnext) == td->tdrd) /* Get next, check it */ + rd = NULL; /* None left! */ + } + /* Flush all vmtrecdefs from rd to end, inclusive */ + if (rd) { + register struct vmtrecdef *pd; + do { + /* Flush vmtrecdef from end, remember its address */ + pd = td->tdrd->rdprev; + td_rddel(td, pd); + } while (rd != pd); /* Keep going until we flushed rd */ + } + + /* Now set up EOT state */ + td->tdcur = NULL; + td->tdrncur = 1; +} + +/* OS-Dependent code + Perhaps someday move into an osdtap.c ? + */ + +#if VMTAPE_ITSDUMP + +#include + +#if CENV_SYS_UNIX +# include +#endif + +/* OS_FULLPATH - Generate full pathname given path of current directory +** plus filename relative to that directory. +** Returns # chars the string would need, including terminating NUL byte. +** If this number is <= maxlen, the string is copied into loc. +** +** Only needed for magtape DUMP creation hacking. +*/ +static int +os_fullpath(char *loc, char *dir, char *file, int maxlen) +{ + register int cnt; + int dlen, flen; + + dlen = strlen(dir); + flen = strlen(file); +#if CENV_SYS_UNIX + if (file[0] == '/') { + dlen = 0; + } else { + char *cp = strrchr(dir, '/'); + dlen = (cp ? 1+(cp-dir) : 0); + } +#else /* Unknown, assume simple append */ +#endif + cnt = dlen+flen+1; + if (cnt <= maxlen) { + if (dlen) strncpy(loc, dir, dlen); + strcpy(loc+dlen, file); + } + return cnt; +} + +/* OS_FMTIME - Find last modified time of file open on stream. +** Only needed for ITS DUMP tape hacking. +*/ +static int +os_fmtime(FILE *f, register struct tm *tm) +{ +#if CENV_SYS_UNIX + struct stat sbuf; + struct tm *stm; + + if ((fstat(fileno(f), &sbuf) >= 0) + && (stm = localtime(&sbuf.st_mtime))) { + *tm = *stm; + return TRUE; + } +#elif CENV_SYS_MAC + FileParam pb; + int err; + struct tm *stm; + + pb.ioFRefNum = f->handle; + pb.ioFDirIndex = 0; + pb.ioVRefNum = 0; + pb.ioNamePtr = NULL; + err = PBGetFInfoSync((ParmBlkPtr)(&pb)); + if ( (err == 0) + && (stm = localtime(&pb.ioFlMdDat))) { + *tm = *stm; + return TRUE; + } + +#else +# error "Unimplemented OS routine os_fmtime()" +#endif + return FALSE; +} + +/* OS_NET2TM - Convert 32-bit Network Time (RFC738) into a TM struct. +** Only needed for magtape DUMP creation hacking. +*/ +static int +os_net2tm(unsigned long ntm, + register struct tm *tm) +{ + time_t utime; + register struct tm *stm; +#if CENV_SYS_UNIX + /* Temporary crock cuz I'm too tired. */ +#define NTM_1_1_76 2398291200UL /* Known ntm value for 1/1/76 (leap year) */ + /* ntm value of Unix epoch (1/1/70) */ +#define NTM_UEPOCH (NTM_1_1_76 - (((365*6)+1) * (24*60*60))) + + if (ntm < NTM_UEPOCH) + return FALSE; + utime = ntm - NTM_UEPOCH; +#endif /* UNIX */ + if (stm = localtime(&utime)) { + *tm = *stm; + return TRUE; + } + return FALSE; +} +#endif /* VMTAPE_ITSDUMP */ diff --git a/src/vmtape.h b/src/vmtape.h new file mode 100644 index 0000000..1772114 --- /dev/null +++ b/src/vmtape.h @@ -0,0 +1,376 @@ +/* VMTAPE.H - Virtual Magnetic-Tape support definitions +*/ +/* $Id: vmtape.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: vmtape.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#ifndef VMTAPE_INCLUDED /* Only include once */ +#define VMTAPE_INCLUDED 1 + +#ifdef RCSID + RCSID(vmtape_h,"$Id: vmtape.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +#include "cenv.h" /* Ensure have OSD target stuff */ + +#ifndef VMTAPE_ARGSTR /* Include mount arg string parsing? */ +# define VMTAPE_ARGSTR 1 +#endif +#ifndef VMTAPE_POPEN /* Include pipe input and decompress? */ +# define VMTAPE_POPEN (CENV_SYS_UNIX) +#endif +#ifndef VMTAPE_ITSDUMP /* Include ITS DUMP tape-creation code? */ +# ifdef KLH10_SYS_ITS +# define VMTAPE_ITSDUMP KLH10_SYS_ITS +# else +# define VMTAPE_ITSDUMP 0 +# endif +#endif + +#ifndef VMTAPE_T20DUMP /* Include T20 DUMPER tape-creation code? */ +# define VMTAPE_T20DUMP 0 /* Not implemented yet */ +#endif +#ifndef VMTAPE_RAW /* Include RAW format code? */ +# define VMTAPE_RAW 1 /* Always; conditional is mainly to clarify code */ +#endif + + +#if VMTAPE_ITSDUMP || VMTAPE_T20DUMP /* Need this if doing word I/O */ +# include "word10.h" +# include "wfio.h" +#endif + +/* TAPE FORMATS + +There are several different classes of formats involved when dealing with +virtual (and real) tapes. + +SOURCE FORMAT +------------- + From VMTAPE's viewpoint, the first is the "source format" of +the file or files that it opens. Such files may be text of a +particular kind, or data with a particular structure. The source +format is normally determined by the file(s) themselves, directly by +their actual contents or indirectly by their filename extension. At runtime +the most you can do is help VMTAPE determine this using this algorithm: + + (1) Caller mandates source format to use regardless of pathname + and without examining any existing contents. + (2) Otherwise, VMTAPE figures it out based on: + Pathname extension given (or found). + If it exists, contents of file examined. + (3) Otherwise, VMTAPE uses a default source format. + Currently this is CTL+RAW. + +Known or possible source formats & extensions: + + CTL { ,.tpk,.tdr} - Control file, text specifies additional source info. + TPC {.tpc} - "TaPe data with Counts" + TPS {.tps,.e11,.tap} - "TaPe data, SIMH format" (Supnik) + TPE (.tpe,.e11,.tap} - "TaPe data, E11 format" (John Wilson) + RAW {.tpr,.tap} - Raw tape data + (Note that ".tap" is now ambiguous.) + +These are tracked internally as VMT_FMT_xxx symbols. + + +APPLICATION FORMAT +------------------ + This is a high level description of the contents of the tape, +based on what application is ultimately expected to read it. For +example, some common PDP-10 backup formats were ITS DUMP, Tenex BSYS, +and DEC DUMPER. It is not necessary to know this format unless +something about the tape generation process depends on it. This is +generally specified at the same time and in the same way as the source +format. + +Known application formats: + + ITSDUMP - ITS DUMP + DUMPER6 - TOPS DUMPER V6 (there are several earlier versions) + +These could be tracked internally as VMTAF_xxx but for now they are +combined with the source format specs. + + +VIRTUAL FORMAT +-------------- + The virtual format is the representation that VMTAPE provides +to the caller. This constitutes the size of a tape frame, the number +of frames in each record, and the occurrence of tape EOF or BOT/EOT +marks. + +Currently this representation is always that of a 9-track tape such +that: + - Each frame provides one 8-bit byte of data + - Records provided as single blocks of bytes + - Tape EOF marks indicated by zero-length blocks + - BOT/EOT indicated by pollable status bits + +This may someday change if, for example, we want to emulate 7-track +tape (6-bit frames) or DECtape (36-bit frames). In that case, the +virtual format would be specified at runtime by the caller, and tracked +internally with VMTVF_xxx symbols. + + +WORD PACKING FORMAT (DATA MODE) +------------------- + Finally for emulator purposes there is a fourth format to +consider, the way in which this virtual tape frame data is packed into +36-bit words (or words of whatever machine is being emulated). For +the PDP-10 tape drives this is called the "data mode". Strictly +speaking this mode is the responsibility of the caller, which will be +emulating a particular tape controller device; however, for convenience +VMTAPE includes provisions for doing word I/O in either "core-dump" or +"industry-compatible" mode. + + +IN PRACTICE - CTL vs NON-CTL +---------------------------- + Because there is presently only one common virtual format, and +the application formats are specified as special cases of the +"Control" source format, in effect the source format specification +almost entirely determines the processing done by VMTAPE. + The "Control" format relies on using an auxiliary text file +to describe the exact structure of the tape; it points to other files +which contain the actual tape data. Any source or application format +can use a control file, although not all of them require it. + + CTL required: ITSDUMP, DUMPER, RAW + CTL optional: TPC, TPS, TPE + +The control file is identified either by an explicit filename +extension (.tpk or the deprecated .tdr), or indirectly by no extension +at all. + + */ +/* +** Currently, the following kinds of tapes can be read: +** VMT_FMT_RAW - General-purpose raw 8-bit frames, with arbitrary tapemarks +** VMT_FMT_TPS - General-purpose using bidirectional record lengths. +** VMT_FMT_TPE - Ditto w/o padding +** VMT_FMT_ITS - read-only, created on-the-fly with no record boundaries +** except at end of each included file, when a tapemark is +** invented. +** The following kinds of tapes can be written: +** VMT_FMT_RAW - as above +** VMT_FMT_TPS - as above +** VMT_FMT_TPE - as above +*/ +#define VMTFF_CTL 0x1 /* Control file (implies XCTL) */ +#define VMTFF_XCTL 0x2 /* Control file must exist (else optional) */ +#define VMTFF_RD 0x4 /* Readable format */ +#define VMTFF_WR 0x8 /* Writable format */ +#define VMTFF_ALIAS 0x100 /* Alias entry - true fmt in VMTFF_FMT */ +#define VMTFF_FMT 0xff /* Alias entry's true format */ + +#define VMTFORMATS \ + vmtfdef(VMT_FMT_UNK, "unknown", NULL, 0),\ + vmtfdef(VMT_FMT_CTL, "ctl", "tpk", VMTFF_CTL),\ + vmtfdef(VMT_FMT_ITS, "its", NULL, VMTFF_CTL|VMTFF_RD),\ + vmtfdef(VMT_FMT_RAW, "raw", "tpr", VMTFF_XCTL|VMTFF_RD|VMTFF_WR),\ + vmtfdef(VMT_FMT_TPC, "tpc", "tpc", VMTFF_RD),\ + vmtfdef(VMT_FMT_TPE, "tpe", "tpe", VMTFF_RD|VMTFF_WR),\ + vmtfdef(VMT_FMT_TPS, "tps", "tps", VMTFF_RD|VMTFF_WR),\ + vmtfdef(VMT_FMT_A_CTL, "ctl", "tdr", VMTFF_ALIAS|VMT_FMT_CTL),\ + vmtfdef(VMT_FMT_A_TAP, "any", "tap", VMTFF_ALIAS|VMTAPE_FMT_DEFAULT),\ + vmtfdef(VMT_FMT_A_TPR, "any", "tap", VMTFF_ALIAS|VMT_FMT_RAW),\ + vmtfdef(VMT_FMT_A_ITS,"itsdump", NULL, VMTFF_ALIAS|VMT_FMT_ITS),\ + vmtfdef(VMT_FMT_N, NULL, NULL, 0) + +/* First alias entry */ +#define VMT_FMT_ALIASES VMT_FMT_A_CTL + +#if 0 /* Possible future formats */ + vmtfdef(VMT_FMT_DUMPER6, "dumper6", NULL, VMTFF_CTL), +#endif + +#define vmtfdef(en, str, ext, flgs) en +enum vmtfmt { VMTFORMATS }; /* mt_format values */ +#undef vmtfdef + +/* Default format. Used when creating a tape, or reading one with a ".tap" +** extension, and none was explicitly specified. +*/ +#ifndef VMTAPE_FMT_DEFAULT +# define VMTAPE_FMT_DEFAULT VMT_FMT_TPS +#endif + + +/* CTL tape directory def */ +struct vmttdrdef { + int tdfmt; /* Format of data in tape data file */ + /* (unused; assumes one byte per frame) */ + char *tdfname; /* M Name of tape data file */ + struct vmtrecdef *tdrd; /* M Pointer to circ list of records/marks */ + struct vmtrecdef *tdcur; /* Current record being read */ + int tdrncur; /* Current # of record in *tdcur */ + long tdrfcnt; /* # frames left to read in current record */ + + /* Stats */ + int tdfils; /* Total # files */ + int tdrecs; /* Total # records */ + long tdbytes; /* Total # bytes (frames) */ + long tdmaxrsiz; /* # bytes in longest record known */ +}; + +/* Tape spec and status block. +** Members marked with 'M' are malloced dynamically. +*/ +struct vmtape { + char *mt_devname; /* Device name, for error output */ + void (*mt_errhan) /* Error handling routine */ + (void *, struct vmtape *, char *); + void *mt_errarg; /* 1st arg provided to errhan */ + int mt_debug; /* TRUE to get debugging output */ + char *mt_filename; /* M Pathname of tapefile to use */ + char *mt_datpath; /* M Pathname of data file */ + FILE *mt_ctlf; /* Cntl file I/O handle */ + FILE *mt_datf; /* Data file I/O handle */ + int mt_ispipe; /* TRUE if datf is a popen'd pipe */ + int mt_writable; /* TRUE if tape can be written */ + enum vmtfmt mt_format; /* VMT_FMT_xxx format specifier */ + struct vmtfmt_s *mt_fmtp; /* Pointer to format table entry */ + int mt_state; /* TS_xxx internal state */ + + /* Tape status - following are updated after each tape op */ + int mt_iowrt; /* TRUE if current IO is write */ + long mt_frames; /* # frames in record just read */ + int mt_err; /* NZ if error in last operation */ + int mt_eof; /* TRUE if just read EOF */ + int mt_eot; /* TRUE if at physical EOT */ + int mt_bot; /* TRUE if at physical BOT */ + + /* CTL (tapedir) file stuff */ + char *mt_contents; /* M Pointer to snarfed ctl file contents */ + int mt_lineno; /* Line number while parsing ctl file */ + int mt_ntdrerrs; /* # of errors parsing ctl file */ + struct vmttdrdef mt_tdr; /* Tape directory info */ + +#if VMTAPE_ITSDUMP + struct vmtfildef *mt_fdefs; /* M Tape files info */ + struct vmtfildef *mt_fcur; /* Current file */ + struct wfile mt_wf; /* For word data i/o on datf */ + w10_t mt_fitsdate; /* ITS date of tapefile creation */ + w10_t *mt_ptr; /* Pointer to tape data */ + int mt_cnt; /* # words still pointed to */ + w10_t mt_wbuf[12]; /* Small temporary buffer */ +#endif +}; + +/* State notes: + + Not all states are used by all formats. The comments document which + are used by which. + + TPS/TPE and RAW share RDATA and RWRITE. It's important for stdio output + to remember whether we're reading or writing since a fseek is required + whenever this changes. +*/ +enum { TS_CLOSED=0, /* All: No tape open */ + TS_THEAD, /* ITSDUMP: Reading tape header */ + TS_FNEXT, /* ITSDUMP: Start reading next file */ + TS_FHEAD, /* ITSDUMP: Reading file header */ + TS_FDATA, /* ITSDUMP: Reading file data */ + TS_FEOF, /* ITSDUMP: Reading tapemark (file EOF) */ + TS_EOT, /* Raw, ITSDUMP: Reading EOT */ + TS_REOF, /* Raw: Read EOF on next input */ + TS_RDATA, /* Raw, TPS/TPE: Idle or Reading data */ + TS_RWRITE, /* Raw, TPS/TPE: Writing tape */ + TS_ERR /* All: Error if try to read */ +}; + + +/* Attributes for vmt_attrparse(), vmt_attrmount() and possibly + other future calls. + */ +struct vmtattrs { + int vmta_mask; +# define VMTA_FMTREQ 0x1 +# define VMTA_FMTDFLT 0x2 +# define VMTA_MODE 0x4 +# define VMTA_STREAM 0x8 +# define VMTA_PATH 0x10 +# define VMTA_CTLPATH 0x20 +# define VMTA_DATPATH 0x40 +# define VMTA_DEV 0x80 +# define VMTA_UNZIP 0x100 +# define VMTA_INMEM 0x200 +# define VMTA_FSKIP 0x400 + enum vmtfmt vmta_fmtreq; /* Explicitly requested format */ + enum vmtfmt vmta_fmtdflt; /* User-specified default format */ + int vmta_mode; +# define VMT_MODE_RDONLY 0x1 /* Read-only; files must exist */ +# define VMT_MODE_UPDATE 0x2 /* Read/Write; files must exist */ +# define VMT_MODE_CREATE 0x4 /* Read/Write; files created, must not exist */ + FILE *vmta_stream; /* File actually this stream */ + int vmta_unzip; /* TRUE to attempt unzips */ + int vmta_inmem; /* TRUE to attempt in-memory operation */ + int vmta_fskip; /* # of files to skip on mount */ + + char vmta_dev[8]; /* Device type (for non-virtual) */ + char vmta_path[128]; + char vmta_ctlpath[128]; + char vmta_datpath[128]; +}; + + + +/* External facilities */ + +extern void vmt_init(struct vmtape *, char *); +#if VMTAPE_ARGSTR +extern int vmt_pathmount(struct vmtape *t, char *path, char *args); +extern int vmt_attrparse(struct vmtape *t, struct vmtattrs *ta, char *args); +#endif +extern int vmt_attrmount(struct vmtape *t, struct vmtattrs *ta); +extern int vmt_unmount(struct vmtape *); +extern int vmt_rewind(struct vmtape *); + +extern void vmt_iobeg(struct vmtape *, int); +extern int vmt_ioend(struct vmtape *); +extern int vmt_rget(struct vmtape *, unsigned char *, size_t); +extern int vmt_rput(struct vmtape *, unsigned char *, size_t); +extern int vmt_eput(struct vmtape *, int, int); + +extern int vmt_eof(struct vmtape *); +extern int vmt_eot(struct vmtape *); +extern int vmt_rspace(struct vmtape *, int, unsigned long); +extern int vmt_fspace(struct vmtape *, int, unsigned long); + +enum vmtfmt vmt_strtofmt(char *); +enum vmtfmt vmt_exttofmt(char *); +#define vmt_tapepath(t) ((t)->mt_filename ? (t)->mt_filename : (t)->mt_datpath) +#define vmt_format(t) ((t)->mt_format) +#define vmt_fmtname(fmt) ((((unsigned)(fmt)) < VMT_FMT_N) ? \ + vmtfmttab[fmt].tf_name : "unknownfmt") + +#define vmt_ismounted(t) ((t)->mt_state != 0) +#define vmt_iswritable(t) ((t)->mt_writable) +#define vmt_isatbot(t) ((t)->mt_bot) +#define vmt_isateof(t) ((t)->mt_eof) +#define vmt_isateot(t) ((t)->mt_eot) +#define vmt_errors(t) ((t)->mt_err) +#define vmt_framecnt(t) ((t)->mt_frames) +#define vmt_frstowds(t,f) ((f)/(t)->mt_fpw) +#define vmt_wdstofrs(t,w) ((w)*(t)->mt_fpw) + +#endif /* ifndef VMTAPE_INCLUDED */ diff --git a/src/wfconv.c b/src/wfconv.c new file mode 100644 index 0000000..98d41ed --- /dev/null +++ b/src/wfconv.c @@ -0,0 +1,143 @@ +/* WFCONV.C - 36-bit word file format conversion filter +*/ +/* $Id: wfconv.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: wfconv.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* + NOTE: due to the fact that PDP-10 words are used as the canonical + input conversion target (and output conversion source), input is + always effectively rounded up to a word boundary. + + This can cause the addition of a few trailing zero bytes when + converting from Text-Newline or Ansi-ASCII format, so WFCONV is + not a general-purpose text-to-text conversion program. For that, + one can use: + + # To insert CR (must use ^V or equiv to quote the ^M) + $ sed 's/$/\^M/' temp.nls > temp.crlfs + + # To delete CR (must use ^V or equiv to quote the ^M) + $ sed 's/\^M$//' temp.crlfs > temp.nls + +*/ + +#include +#include /* Malloc and friends */ +#include +#include +#include + +#include "rcsid.h" +#include "word10.h" +#include "wfio.h" + +#ifdef RCSID + RCSID(wfconv_c,"$Id: wfconv.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +#define WFIO_DEBUG 0 + +#include "wfio.c" /* Inclusion, so that WFIO_DEBUG works */ + +static int wtypesw(int ch); +static char usage[] = "\ +Usage: wfconv -io outfile\n\ + where 'i' and 'o' are chars specifying the input and output formats:\n\ + c - Core-dump (std tape format, 4 8-bit, 1 4-bit bytes = 36 bits)\n\ + h - High-density (FTP 36-bit image, 9 8-bit bytes = 72 bits)\n\ + a,7 - Ansi-Ascii (4 7-bit, 1 8-bit byte = 36 bits)\n\ + s,6 - Sixbit (6 6-bit bytes = 36 bits)\n\ + u - Unixified (Alan Bawden format, various = 36 bits)\n\ + t - Text-Newline (CRLF-NL conversion; 5 7-bit bytes = 35 bits ONLY)\n\ + d - Debug (output only - show word values)\n\ + D - Debug (like -d with offsets)\n\ + Note: EOF on input always zero-pads up to a PDP-10 word boundary.\n\ +"; + +static int dbgout = 0; /* 1 for d, 2 for D (offsets) */ + +int +main(int argc, char **argv) +{ + char *typestr; + int from, to; + struct wfile wfrom, wto; + w10_t w; + + if (argc != 2 || strlen(typestr = argv[1]) != 3) { + fprintf(stderr, usage); + exit(1); + } + if (typestr[0] != '-' + || (from = wtypesw(typestr[1])) < 0) { + fprintf(stderr, "%s: unknown input conversion '%c'\n%s", + argv[0], typestr[1], usage); + exit(1); + } + if (typestr[2] == 'd') { + dbgout = 1; + } else if (typestr[2] == 'D') { + dbgout = 2; + } else if ((to = wtypesw(typestr[2])) < 0) { + fprintf(stderr, "%s: unknown output conversion '%c'\n%s", + argv[0], typestr[2], usage); + exit(1); + } + + wf_init(&wfrom, from, stdin); + + if (dbgout) { /* Debug output, just show value */ + long wloc = 0; + while (wf_get(&wfrom, &w) > 0) { + if (dbgout == 2) + fprintf(stdout, "%#lo: ", wloc++); + fprintf(stdout, "%6lo,,%6lo\n", (long)LHGET(w), (long)RHGET(w)); + } + } else { + wf_init(&wto, to, stdout); + + while (wf_get(&wfrom, &w) > 0) + if (wf_put(&wto, w) <= 0) { + fprintf(stderr, "Output error, aborting\n"); + break; + } + wf_flush(&wto); + } + + fclose(stdout); + exit(0); +} + +static int wtypesw(int ch) +{ + char cbuf[4]; + char *cp = cbuf; + + strcpy(cp, "x36"); /* Set up to let wf_type do the work */ + switch (ch) { /* Handle any special synonyms */ + default: *cp = ch; break; + case 't': + case 'T': cp = "tnl"; break; + } + return wf_type(cp); +} + diff --git a/src/wfio.c b/src/wfio.c new file mode 100644 index 0000000..72e92ea --- /dev/null +++ b/src/wfio.c @@ -0,0 +1,793 @@ +/* WFIO.C - 36-bit Word File I/O facilities +*/ +/* $Id: wfio.c,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: wfio.c,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* +File formats supported: + + u36 - Alan Bawden's unixified stuff + h36 - High-density mode - same as FTP 36-bit Image. + All bits used in all bytes. + byte 0 - high 8 bits of word + byte 5 - low 4 bits of word in high end, high 4 bits of + next word in low end. + Thus 9 bytes for every 2 words. + c36 - Core-Dump mode - standard tape format. + a36 - ANSI-ASCII mode - 5 bytes, 7 bits per byte (high bit clear) + except in last byte, where low bit (35) of word is high bit + of byte. + s36 - Sixbit mode - 6 bytes, 6 bits/byte in low bits. Was mainly + for 7-track tapes. + nlt - Newline Text. On input, converts NL to CR-LF; on output, + converts CR-LF to NL. + + TAPE FORMATS: + + S36: Tape_Sixbit (6) + 0 0 B0 1 2 3 4 5 + 0 0 6 7 8 9 10 11 + 0 0 12 13 14 15 16 17 + 0 0 18 19 20 21 22 23 + 0 0 24 25 26 27 28 29 + 0 0 30 31 32 33 34 35 + + C36: Tape_Coredump (5) + B0 1 2 3 4 5 6 7 + 8 9 10 11 12 13 14 15 + 16 17 18 19 20 21 22 23 + 24 25 26 27 28 29 30 31 + 0 0 0 0 32 33 34 35 + + A36: Tape_Ascii (5) + 0 B0 1 2 3 4 5 6 + 0 7 8 9 10 11 12 13 + 0 14 15 16 17 18 19 20 + 0 21 22 23 24 25 26 27 + 35 28 29 30 31 32 33 34 + + H36: Tape_Hidens, Tape_FTP (9/2) + B0 1 2 3 4 5 6 7 + 8 9 10 11 12 13 14 15 + 16 17 18 19 20 21 22 23 + 24 25 26 27 28 29 30 31 + 32 33 34 35 B0 1 2 3 + 4 5 6 7 8 9 10 11 + 12 13 14 15 16 17 18 19 + 20 21 22 23 24 25 26 27 + 28 29 30 31 32 33 34 35 + + DISK FORMATS: + + dbd9: Disk_BigEnd_Double (9/2) - Same as H36 (Tape_Hidens) + + dld9: Disk_LittleEnd_Double (9/2) + 28 29 30 31 32 33 34 35 + 20 21 22 23 24 25 26 27 + 12 13 14 15 16 17 18 19 + 4 5 6 7 8 9 10 11 + 32 33 34 35 B0 1 2 3 + 24 25 26 27 28 29 30 31 + 16 17 18 19 20 21 22 23 + 8 9 10 11 12 13 14 15 + B0 1 2 3 4 5 6 7 + + dbh4: Disk_BigEnd_Halfword (8) + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 B0 1 + 2 3 4 5 6 7 8 9 + 10 11 12 13 14 15 16 17 + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 18 19 + 20 21 22 23 24 25 26 27 + 28 29 30 31 32 33 34 35 + + dlh4: Disk_LittleEnd_Halfword (8) + 28 29 30 31 32 33 34 35 + 20 21 22 23 24 25 26 27 + 0 0 0 0 0 0 18 19 + 0 0 0 0 0 0 0 0 + 10 11 12 13 14 15 16 17 + 2 3 4 5 6 7 8 9 + 0 0 0 0 0 0 B0 1 + 0 0 0 0 0 0 0 0 + + dbw8: Disk_BigEnd_Word (8) + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 + 0 0 0 0 B0 1 2 3 + 4 5 6 7 8 9 10 11 + 12 13 14 15 16 17 18 19 + 20 21 22 23 24 25 26 27 + 28 29 30 31 32 33 34 35 + + dlw8: Disk_LittleEnd_Word (8) + 28 29 30 31 32 33 34 35 + 20 21 22 23 24 25 26 27 + 12 13 14 15 16 17 18 19 + 4 5 6 7 8 9 10 11 + 0 0 0 0 B0 1 2 3 + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 +*/ + +#include +#include /* Malloc and friends */ +#include +#include +#include + +#include "rcsid.h" +#include "word10.h" +#include "wfio.h" + +#ifdef RCSID + RCSID(wfio_c,"$Id: wfio.c,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +#ifndef WFIO_DEBUG /* Include debug output capability if 1 */ +# define WFIO_DEBUG 0 +#endif + +static int wfu_get(struct wfile *, w10_t *), + wfu_gasc(struct wfile *, w10_t *, int); + +static char *wftnames[] = { +# define wtdef(i,n) n + WF_TYPENAMDEFS +# undef wtdef +}; +#define WF_NTYPES (sizeof(wftnames)/sizeof(wftnames[0])) + +void +wf_init(register struct wfile *wf, int type, FILE *f) +{ + wf->wff = f; + wf->wfsiop = ftell(f); + wf->wfloc = 0; + wf->wflastch = WFC_NONE; + wf->wferrlen = wf->wferrfmt = 0; + wf->wftype = type; + if (0 <= wf->wftype && wf->wftype < WF_NTYPES) + wf->wftypnam = wftnames[wf->wftype]; + else { /* No way to indicate error yet */ + wf->wftypnam = "???"; + } +} + +int +wf_type(char *nam) +{ + if (strlen(nam) != 3) + return -1; /* Not a name we know about yet */ + if (nam[1] == '3' && nam[2] == '6') + switch (nam[0]) { + case 'u': case 'U': return WFT_U36; + case 'h': case 'H': return WFT_H36; + case 'c': case 'C': return WFT_C36; + case '7': + case 'a': case 'A': return WFT_A36; + case '6': + case 's': case 'S': return WFT_S36; + default: + return -1; + } + if (strcasecmp(nam, "tnl") == 0) + return WFT_TNL; + return -1; +} + +/* WFILE seeking. + +This is tricky primarily for two cases: U36 and H36. + + U36 cannot reliably implement wf_seek for reading, because we have +no idea how many bytes are being used to represent any particular words +(without actually reading them in). Writing is OK because we always +use the simple-minded tactic of using binary format, 5 bytes per word. + + H36 does know where to go, but has a problem because the two +words of each double-word share bits from a single byte. This is excerbated +by the fact that we don't know ahead of time whether the next operation +will be a read or a write; in addition to this, ANSI C requires that a +fflush() or fseek() occur between sequences of reads and writes. + To resolve this, the wflastch variable is used as a state +indicator. If it is set to: + WFC_NONE - the I/O pointer is at the start of a word pair, + either reading or writing. + WFC_PREREAD - the I/O pointer is logically at the start of the + 2nd word of a pair, but actually is pointing to the byte + shared by the 2 words, and must read that byte before + either reading or writing the next word. + > WFC_NONE - as above, but wflastch already contains the needed 4 + bits, right-justified. It will have the bit WFC_WVAL set + if the last operation was a wf_put as opposed to a wf_get. + +One other fine detail: in order to implement arbitrary seeks and writes into +existing data for H36 format, wf_flush() must be called before closing the +stream, and it must take care of forcing out the last 4 bits of the +1st word of a pair, so that the bottom 4 bits of existing file data for that +byte aren't clobbered to 0. + +*/ + + +#ifndef SEEK_SET +# define SEEK_SET 0 /* Usual pre-ANSI value */ +#endif +#ifndef SEEK_CUR +# define SEEK_CUR 1 /* Usual pre-ANSI value */ +#endif + +int +wf_rewind(register struct wfile *wf) +{ + return wf_seek(wf, (long)0); +} + +int +wf_seek(register struct wfile *wf, long loc) +{ + long offset; + + switch (wf->wftype) { + case WFT_U36: /* Alan Bawden Unixified format */ + case WFT_TNL: + /* If reading, it's hopeless. If writing, we have a chance. + ** So assume writing, 5 bytes per word. + */ + wf->wflastch = WFC_NONE; /* Reset in case reading */ + offset = loc * 5; + break; + + case WFT_H36: /* High-density (also FTP Image) format */ + if (!wf_flush(wf)) /* Ensure stuff forced out, reset */ + return 0; + offset = (loc / 2) * 9; + if (loc & 01) { /* If seeking to 2nd of a pair, moby hair! */ + wf->wflastch = WFC_PREREAD; + offset += 4; + } else + wf->wflastch = WFC_NONE; + break; + + case WFT_C36: /* Core-dump (aka tape) format */ + case WFT_A36: /* Ansi-Ascii (7-bit) format */ + offset = loc * 5; + break; + + case WFT_S36: /* SIXBIT (7-track tape) format */ + offset = loc * 6; + break; + + default: + return 0; + } + + if (fseek(wf->wff, wf->wfsiop + offset, SEEK_SET)) { + return 0; + } + wf->wfloc = loc; + return 1; +} + +int +wf_flush(register struct wfile *wf) +{ + switch (wf->wftype) { + case WFT_U36: /* Alan Bawden Unixified format */ + /* Using full binary encoding by default, no funny stuff */ + break; + + case WFT_H36: /* High-density (also FTP Image) format */ + if (wf->wflastch >= WFC_WVAL) { + register int ch; + + fflush(wf->wff); /* Permit switch to reading */ + ch = getc(wf->wff); /* Get existing byte */ + if (ch != EOF) { + ch = ((wf->wflastch & 017) << 4) | (ch & 017); + if (fseek(wf->wff, (long)-1, SEEK_CUR) != 0) /* Back up */ + return 0; + } else ch = (wf->wflastch & 017) << 4; + putc(ch, wf->wff); /* Force out last write byte */ + wf->wflastch = WFC_NONE; + } + break; + + case WFT_C36: /* Core-dump (aka tape) format */ + case WFT_A36: /* Ansi-Ascii (7-bit) format */ + case WFT_S36: /* SIXBIT (7-track tape) format */ + break; + + case WFT_TNL: /* Text (7-bit) format */ + if (wf->wflastch == WFC_CR) { /* Last char a pending CR? */ +#if 1 /* Problematic because flushing out the bare CR is only OK + if no more words are to be output. if any more follow + then it's likely a LF will be present - thus the output + of the CR would be incorrect. + For now, assume wf_flush is only called when about to + close the stream; this is true for WFCONV which is the + only thing likely to be using WFT_TNL anyway. + */ + putc('\r', wf->wff); +#endif + wf->wflastch = WFC_NONE; /* No char saved */ + } + break; + + default: + return 0; + } + + fflush(wf->wff); + return !ferror(wf->wff); +} + +int +wf_get(register struct wfile *wf, w10_t *wp) +{ + register FILE *f; + register int i; + uint18 cbuf[6]; + register int cix; + + if (wf->wftype == WFT_U36) /* Alan Bawden Unixified format */ + return wfu_get(wf, wp); + + /* Check first byte; if none, no more words. */ + f = wf->wff; + if ((i = getc(f)) == EOF) { + /* But TNL format may still have something left over */ + if ((wf->wftype == WFT_TNL) + && (wf->wflastch == WFC_LF)) + i = 0; + else + return 0; + } + + switch (wf->wftype) { + case WFT_H36: /* High-density (also FTP Image) format */ + cbuf[0] = i & 0377; + cbuf[1] = ((i = getc(f)) == EOF) ? 0 : (i & 0377); + cbuf[2] = ((i = getc(f)) == EOF) ? 0 : (i & 0377); + cbuf[3] = ((i = getc(f)) == EOF) ? 0 : (i & 0377); + switch (wf->wflastch) { + case WFC_NONE: /* First word of a pair */ + cbuf[4] = ((i = getc(f)) == EOF) ? 0 : (i & 0377); + XWDPSET(wp, + ( ((uint18)cbuf[0] << 10) + | (cbuf[1] << 2) + | (cbuf[2] >> 6)), + ( ((uint18)(cbuf[2] & 077) << 12) + | (cbuf[3] << 4) + | (cbuf[4] >> 4)) /* High 4 bits of last byte */ + ); + wf->wflastch = cbuf[4] & 017; /* Remember low 4 bits */ + break; + + case WFC_PREREAD: /* Gross hack: 2nd word, after wf_seek */ + cbuf[4] = ((i = getc(f)) == EOF) ? 0 : (i & 0377); + XWDPSET(wp, + ( ((uint18)(cbuf[0]&017) << 14) /* Low 4 bits of prev byte */ + | (cbuf[1] << 6) + | (cbuf[2] >> 2)), + ( ((uint18)(cbuf[2] & 03) << 16) + | (cbuf[3] << 8) + | (cbuf[4])) + ); + wf->wflastch = WFC_NONE; + break; + + default: /* Second word of a pair, after wf_get */ + if (wf->wflastch >= WFC_WVAL) + return -1; /* Error, last op was a wf_put! */ + XWDPSET(wp, + ( ((uint18)wf->wflastch << 14) /* Low 4 bits of prev byte */ + | (cbuf[0] << 6) + | (cbuf[1] >> 2)), + ( ((uint18)(cbuf[1] & 03) << 16) + | (cbuf[2] << 8) + | (cbuf[3])) + ); + wf->wflastch = WFC_NONE; + break; + } + break; + + case WFT_C36: /* Core-dump (aka tape) format */ + cbuf[0] = i & 0377; + cbuf[1] = ((i = getc(f)) == EOF) ? 0 : (i & 0377); + cbuf[2] = ((i = getc(f)) == EOF) ? 0 : (i & 0377); + cbuf[3] = ((i = getc(f)) == EOF) ? 0 : (i & 0377); + cbuf[4] = ((i = getc(f)) == EOF) ? 0 : (i & 0377); + XWDPSET(wp, + ( ((uint18)cbuf[0] << 10) + | (cbuf[1] << 2) + | (cbuf[2] >> 6)), + ( ((uint18)(cbuf[2] & 077) << 12) + | (cbuf[3] << 4) + | (cbuf[4] & 017)) /* Low 4 bits of last byte */ + ); + break; + + case WFT_A36: /* Ansi-Ascii (7-bit) format */ + cbuf[0] = i & 0177; + cbuf[1] = ((i = getc(f)) == EOF) ? 0 : (i & 0177); + cbuf[2] = ((i = getc(f)) == EOF) ? 0 : (i & 0177); + cbuf[3] = ((i = getc(f)) == EOF) ? 0 : (i & 0177); + cbuf[4] = ((i = getc(f)) == EOF) ? 0 : + ((i & 0177) << 1) | ((i & 0200) >> 7); /* Keep 8th */ + XWDPSET(wp, + ( ((uint18)cbuf[0] << 11) + | (cbuf[1] << 4) + | (cbuf[2] >> 3)), + ( ((uint18)(cbuf[2] & 07) << 15) + | (cbuf[3] << 8) + | cbuf[4] ) + ); + break; + + case WFT_TNL: /* Unix NL text (7-bit) format */ + /* Convert all incoming NL chars to CR-LF sequence */ + cix = 0; + if (wf->wflastch == WFC_LF) { /* Have leftover LF from last wd? */ + cbuf[cix++] = WFC_LF; /* Yep, start with it */ + wf->wflastch = WFC_NONE; /* and reset */ + } + /* Loop until have 5 input chars */ + for (;;) { + if ((i &= 0177) == '\n') { + cbuf[cix] = WFC_CR; /* First output PDP-10 ASCII CR */ + i = WFC_LF; /* Ensure next is PDP-10 ASCII LF */ + if (++cix >= 5) { /* If no room for LF after CR insert */ + wf->wflastch = i; /* then save for next word */ + break; + } + } + cbuf[cix] = i; + if (++cix >= 5) + break; + if ((i = getc(f)) == EOF) + i = 0; + } + XWDPSET(wp, + ( ((uint18)cbuf[0] << 11) + | (cbuf[1] << 4) + | (cbuf[2] >> 3) ), + ( ((uint18)(cbuf[2] & 07) << 15) + | (cbuf[3] << 8) + | (cbuf[4] << 1) ) + ); + break; + + case WFT_S36: /* SIXBIT (7-track tape) format */ + cbuf[0] = i & 077; + cbuf[1] = ((i = getc(f)) == EOF) ? 0 : (i & 077); + cbuf[2] = ((i = getc(f)) == EOF) ? 0 : (i & 077); + cbuf[3] = ((i = getc(f)) == EOF) ? 0 : (i & 077); + cbuf[4] = ((i = getc(f)) == EOF) ? 0 : (i & 077); + cbuf[5] = ((i = getc(f)) == EOF) ? 0 : (i & 077); + XWDPSET(wp, + ( ((uint18)cbuf[0] << 12) | (cbuf[1] << 6) | cbuf[2] ), + ( ((uint18)cbuf[3] << 12) | (cbuf[4] << 6) | cbuf[5] ) + ); + break; + } + + if (feof(f)) { + wf->wferrlen++; /* Error in unix length of file */ + if (wf->wfdebug) + fprintf(stderr, "Unexpected EOF in middle of PDP-10 word.\n"); + } + return 1; +} + +int +wf_put(register struct wfile *wf, + register w10_t w) +{ + register FILE *f; + register int n; + register unsigned char *cp; + unsigned char cbuf[10]; + + switch (wf->wftype) { + case WFT_U36: /* Alan Bawden Unixified format */ + /* Use full binary encoding by default, no funny stuff */ + cbuf[0] = 0360 | ((LHGET(w)>>14)&017); + cbuf[1] = (LHGET(w)>> 6)&0377; + cbuf[2] = (((LHGET(w)&077)<<2) | ((RHGET(w)>>16)&003)); + cbuf[3] = (RHGET(w)>>8)&0377; + cbuf[4] = RHGET(w)&0377; + n = 5; + break; + + case WFT_H36: /* High-density (also FTP Image) format */ + switch (wf->wflastch) { + case WFC_NONE: /* First word of a pair */ + cbuf[0] = (LHGET(w)>>10)&0377; + cbuf[1] = (LHGET(w)>> 2)&0377; + cbuf[2] = (((LHGET(w)&03)<<6) | ((RHGET(w)>>12)&077)); + cbuf[3] = (RHGET(w)>>4)&0377; + n = 4; + wf->wflastch = (RHGET(w) & 017) | WFC_WVAL; /* Save last 4 bits */ + break; + + default: /* Second word of pair, after wf_put */ + if (wf->wflastch < WFC_WVAL) + return -1; /* Last op was a wf_get?! */ + cbuf[0] = ((wf->wflastch&017)<<4) | ((LHGET(w)>>14)&017); + cbuf[1] = (LHGET(w)>>6)&0377; + cbuf[2] = (((LHGET(w)&077)<<2) | ((RHGET(w)>>16)&03)); + cbuf[3] = (RHGET(w)>>8)&0377; + cbuf[4] = (RHGET(w) )&0377; + n = 5; + wf->wflastch = WFC_NONE; + break; + + case WFC_PREREAD: /* Second word of a pair, after wf_seek */ + n = getc(wf->wff); /* Find byte val */ + if (n == EOF) /* Back up one */ + n = 0; + if (fseek(wf->wff, (long)-1, SEEK_CUR) != 0) + return -1; + cbuf[0] = (n & (017<<4)) /* Save high 4 bits */ + | ((LHGET(w)>>14)&017); + cbuf[1] = (LHGET(w)>>6)&0377; + cbuf[2] = (((LHGET(w)&077)<<2) | ((RHGET(w)>>16)&03)); + cbuf[3] = (RHGET(w)>>8)&0377; + cbuf[4] = (RHGET(w) )&0377; + n = 5; + wf->wflastch = WFC_NONE; + break; + } + break; + + case WFT_C36: /* Core-dump (aka tape) format */ + cbuf[0] = (LHGET(w)>>10)&0377; + cbuf[1] = (LHGET(w)>> 2)&0377; + cbuf[2] = (((LHGET(w)&03)<<6) | ((RHGET(w)>>12)&077)); + cbuf[3] = (RHGET(w)>>4)&0377; + cbuf[4] = RHGET(w)&017; /* Put last 4 bits in low end */ + n = 5; + break; + + case WFT_A36: /* Ansi-Ascii (7-bit) format */ + cbuf[0] = (LHGET(w)>>11)&0177; + cbuf[1] = (LHGET(w)>> 4)&0177; + cbuf[2] = (((LHGET(w)&017)<<3) | ((RHGET(w)>>15)&07)); + cbuf[3] = (RHGET(w)>>8)&0177; + cbuf[4] = ((RHGET(w)>>1)&0177) | ((RHGET(w)&01)<<7); + n = 5; + break; + + case WFT_TNL: /* Text (7-bit) format */ + /* Convert all CR-LF sequences to just NL */ + cp = cbuf; + if (wf->wflastch == WFC_CR) { /* Last char a pending CR? */ + *cp++ = WFC_CR; /* Re-insert into our buffer */ + wf->wflastch = WFC_NONE; /* No char saved */ + n = 6; + } else n = 5; + + *cp++ = (LHGET(w)>>11)&0177; + *cp++ = (LHGET(w)>> 4)&0177; + *cp++ = (((LHGET(w)&017)<<3) | ((RHGET(w)>>15)&07)); + *cp++ = (RHGET(w)>>8)&0177; + *cp = (RHGET(w)>>1)&0177; + + f = wf->wff; + for (cp = cbuf; --n >= 0;) { + if (*cp == WFC_CR) { /* If see CR, test for LF */ + if (--n < 0) { /* ensure a char follows */ + wf->wflastch = WFC_CR; /* Nope, save the CR */ + return !ferror(f); + } + if (*++cp == WFC_LF) /* CR-LF sequence? */ + *cp = '\n'; /* Yep, replace by native NL */ + else + putc('\r', f); /* Nope, let native CR pass through */ + } + putc(*cp++, f); + } + return !ferror(f); + + case WFT_S36: /* SIXBIT (7-track tape) format */ + cbuf[0] = (LHGET(w)>>12)&077; + cbuf[1] = (LHGET(w)>> 6)&077; + cbuf[2] = (LHGET(w) )&077; + cbuf[3] = (RHGET(w)>>12)&077; + cbuf[4] = (RHGET(w)>> 6)&077; + cbuf[5] = (RHGET(w) )&077; + n = 6; + break; + + default: + return -1; + } + + f = wf->wff; + for (cp = cbuf; --n >= 0;) + putc(*cp++, f); + return !ferror(f); +} + +static int +wfu_get(register struct wfile *wf, + w10_t *aw) +{ + register FILE *f = wf->wff; + register uint18 lh, rh; + register int i; + unsigned int cbuf[5]; /* 16 bits is sufficient */ + register unsigned ch; +#if WFIO_DEBUG + wf->wftp = wf->wftbuf; +#endif + if (wf->wflastch != WFC_NONE) { + /* Start this word using last (virtual) char. By definition + ** that means the rest of this word uses the 7-bit encoding. + */ + return wfu_gasc(wf, aw, wf->wflastch); + } + if ((lh = getc(f)) == EOF) + return 0; +#if WFIO_DEBUG + *wf->wftp++ = lh; +#endif + wf->wfloc++; + + /* First byte determines how rest of encoding will go */ + if ((lh & 0360) != 0360) { + return wfu_gasc(wf, aw, lh); + } + lh = (lh&017) << (18-4); /* Set 4 bits 740000,,0 */ + + for (i = 0; ++i <= 4; ) { + if ((ch = getc(f)) == EOF) + cbuf[i] = 03; /* ITS pads with ^C */ + else { + cbuf[i] = ch; + wf->wfloc++; + } +#if WFIO_DEBUG + *wf->wftp++ = ch; +#endif + } + + /* Put binary word together. Coded to keep int shifts within 16 bits. */ + lh |= (cbuf[1]&0377) << (18-12); /* Set 8 bits 37700,,0 */ + rh = cbuf[2] & 0377; + lh |= (rh >> 2) & 077; /* Set 6 bits 77,,0 */ + rh = (rh & 03) << (18-2); /* and 2 bits ,,600000 */ + rh |= (cbuf[3]&0377) << (18-10); /* Set 8 bits ,,177400 */ + rh |= (cbuf[4]&0377); /* Set 8 bits ,,000377 */ + LRHSET(*aw, lh, rh); + +#if WFIO_DEBUG + if (wf->wfdebug) { + register int i = wf->wftp - wf->wftbuf; + register unsigned char *tp = wf->wftbuf; + for (; --i >= 0; ) + printf(" %3o", *tp++); + printf(" BIN %6lo,,%6lo\n", (long) LHGET(*aw), (long) RHGET(*aw)); + } +#endif + if (feof(f)) { + wf->wferrlen++; /* Error in unix length of file */ + if (wf->wfdebug) + fprintf(stderr, "Unexpected EOF in middle of PDP-10 word.\n"); + } + return 1; /* Won, word deposited */ +} + +static int +wfu_gasc(register struct wfile *wf, + register w10_t *aw, + register int ch) +{ + register int i = 0; + int cbuf[5]; + + + if (ch == wf->wflastch) { + cbuf[i++] = ch; /* Stuff first char in immediately, no check */ + ch = getc(wf->wff); +#if WFIO_DEBUG + *wf->wftp++ = ch; +#endif + } + + wf->wflastch = WFC_NONE; + for (;;) { + switch (ch) { + case EOF: + wf->wferrlen++; /* Error in unix length of file */ + if (wf->wfdebug) + fprintf(stderr, "Unexpected EOF in middle of PDP-10 word.\n"); + ch = 03; /* ITS pads with ^C */ + break; + + case 012: cbuf[i++] = 015; break; + case 015: ch = 012; break; + case 0177: cbuf[i++] = 0177; ch = 07; break; + case 0207: cbuf[i++] = 0177; ch = 0177; break; + case 0212: cbuf[i++] = 0177; ch = 015; break; + case 0215: cbuf[i++] = 0177; ch = 012; break; + case 0356: ch = 015; break; + case 0357: ch = 0177; break; + default: + if (ch < 0177) break; + if (ch < 0360) { cbuf[i++] = 0177; ch -= 0200; break; } + wf->wferrfmt++; /* Error in unixify of this file */ + fprintf(stderr, "Unexpected binary-byte in middle of word!\n"); + fprintf(stderr, " cbuf:"); + { register int cnt; + for(cnt = 0; cnt < i; ++cnt) + printf(" %o", cbuf[cnt]); + printf(" Bad byte: %o File location: %ld.\n", ch, + wf->wfloc-1); + } + return -1; + } + /* Deposit char. If it's an extra one, put in wflastch */ + if (i >= 5) { + wf->wflastch = ch; + break; + } + cbuf[i++] = ch; + if (i >= 5) + break; + if ((ch = getc(wf->wff)) != EOF) + wf->wfloc++; +#if WFIO_DEBUG + *wf->wftp++ = ch; +#endif + } + + /* OK, now put together word from the 5 chars we gobbled. If there + ** was an extra virtual char, it was left in wflastch for the next call. + */ + LHSET(*aw, ((uint18)cbuf[0]<<(18-7)) | (cbuf[1]<<(18-14)) | (cbuf[2]>>3)); + RHSET(*aw, ((uint18)(cbuf[2]&07)<<(18-3)) | (cbuf[3]<<(18-10)) | (cbuf[4]<<1)); + +#if WFIO_DEBUG + if (wf->wfdebug) { + register int i = wf->wftp - wf->wftbuf; + register unsigned char *cp; + ch = 5-i; + for (cp = wf->wftbuf; --i >= 0; ) + printf(" %3o", *cp++); + while (--ch >= 0) + printf(" -- "); + printf(" Asc %6lo,,%6lo [", (long) LHGET(*aw), (long) RHGET(*aw)); + for (ch = 0; ch < 5; ++ch) + printf(" %3o", cbuf[ch]); + printf("]\n"); + } +#endif + return 1; +} + diff --git a/src/wfio.h b/src/wfio.h new file mode 100644 index 0000000..1bd13c8 --- /dev/null +++ b/src/wfio.h @@ -0,0 +1,78 @@ +/* WFIO.H - 36-bit Word File I/O facilities +*/ +/* $Id: wfio.h,v 2.3 2001/11/10 21:28:59 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: wfio.h,v $ + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +#ifndef WFIO_INCLUDED +#define WFIO_INCLUDED 1 + +#ifdef RCSID + RCSID(wfio_h,"$Id: wfio.h,v 2.3 2001/11/10 21:28:59 klh Exp $") +#endif + +#include /* Needed for FILE definition */ + +#define WF_TYPENAMDEFS \ + wtdef(WFT_U36, "u36"), /* Unixified (Alan Bawden) */\ + wtdef(WFT_H36, "h36"), /* High-density (FTP Image) */\ + wtdef(WFT_C36, "c36"), /* Core-dump (std tape) */\ + wtdef(WFT_A36, "a36"), /* Ansi-Ascii (7-bit) */\ + wtdef(WFT_S36, "s36"), /* SIXBIT (7-track) */\ + wtdef(WFT_TNL, "tnl") /* Text, Newline conversion (only 35 bits) */ + +enum wftypes { +# define wtdef(i,n) i + WF_TYPENAMDEFS +# undef wtdef +}; + +struct wfile { + enum wftypes wftype; + FILE *wff; + char *wftypnam; /* Name of type (returned by wf_typnam) */ + int wferrfmt; + int wferrlen; + long wfloc; /* Word offset from starting location in file */ + long wfsiop; /* Starting location in file (as a stdio pointer) */ + int wflastch; /* May hold last char read (U36, H36, TNL only) */ +#define WFC_NONE (-1) /* No value in lastch */ +#define WFC_PREREAD (-2) /* Must pre-read to get value (H36 only) */ +#define WFC_WVAL (1<<7) /* Min val saying last op was output (H36) */ +#define WFC_CR (015) /* ASCII CR */ +#define WFC_LF (012) /* ASCII LF */ + int wfdebug; + unsigned char *wftp, /* Only used if WF_DEBUG is true */ + wftbuf[5]; +}; +#define WFILE struct wfile /* Analogous to stdio FILE */ + +extern void wf_init(WFILE *, int, FILE *); +extern int wf_type(char *); /* Given typename, return type # */ +extern int wf_rewind(WFILE *); +extern int wf_seek(WFILE *, long); +extern int wf_flush(WFILE *); +extern int wf_get(WFILE *, w10_t *); +extern int wf_put(WFILE *, w10_t); + +#define wf_typnam(wf) ((wf)->wftypnam) /* Return name of WF type */ + +#endif /* ifndef WFIO_INCLUDED */ diff --git a/src/word10.h b/src/word10.h new file mode 100644 index 0000000..b1ca579 --- /dev/null +++ b/src/word10.h @@ -0,0 +1,918 @@ +/* WORD10.H - Definitions and minor operations on PDP-10 36-bit words +*/ +/* $Id: word10.h,v 2.4 2001/11/19 10:37:38 klh Exp $ +*/ +/* Copyright © 1992, 1993, 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: word10.h,v $ + * Revision 2.4 2001/11/19 10:37:38 klh + * Fixed USEGCCSPARC to work again for Suns. + * + * Revision 2.3 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* + Define representational architecture of a 36-bit PDP-10 word. + +There are many possible ways to represent a 36-bit word, but only a +few specific methods are provided for. All of them rely on the following +basic type definitions, which specify the C integer types of the native +compiler in terms of their size in N bits. Each may have more than N, +but cannot have less. + +MANDATORY (must be defined): (native C integer type of at least N bits) + WORD10_INT18 = <18+> + WORD10_INT19 = <19+> + WORD10_INT32 = <32+> +Optional (may be undefined): + WORD10_INT36 = <36 exactly> + WORD10_INT37 = <37+> + WORD10_INT64 = <64+> + +If the mandatory types (18, 19, and 32 bits) are not predefined, they +will be automatically determined from limits.h; ANSI C guarantees that +a 32+ bit type will always exist. The other types are always optional +unless specifically required by a particular model. Certain models +can check to see if larger types exist and do some things more +efficiently if so, but this is not required -- partly because all of +the possible combinations quickly become unpleasantly messy. + +(1) USEHWD model: Halfword structure + WORD10_USEHWD = 1 + This representation declares a w10_t as a structure of two +halfwords, with each 18-bit halfword right-justified in a native +integer type of at least 18 bits. This is the only model that will +work on machines without a native C integer type of at least 36 bits. +The ordering of the two halfwords in memory is irrelevant. + +(2) USENAT model: Native 36-bit integer + WORD10_USENAT = 1 + WORD10_INT36 = + This model can be used if a native C integer type of exactly 36 +bits is available. Currently this implies using the KCC compiler on a +real PDP-10. I don't expect this to be useful as more than a +curiousity, but it is easy to do except for emulating arithmetic flags. + +(3) USEINT: Native 37+ bit integer + WORD10_USEINT = 1 + WORD10_INT37 = = 37 bits> + This model holds the 36-bit word right-justified in the native +type. This is the basic one to use if a native type of more than 36 +bits is available (typically 64). However, depending on the host +system's C compiler or machine architecture, USEHUN may be a more +efficient alternative. Note also the possibility of adding WORD10_BITF to +this model. + +(4) USEHUN: Hybrid union; both halfword structure and native 37+bit integer. + WORD10_USEHUN = 1 + WORD10_INT37 = = 37 bits> + This model is an alternative to USEINT which might be more desirable +on some platforms where the 37+bit type has natural 18+bit type boundaries. +It is a hybrid of USEHWD and USEINT where a halfword structure is laid over +a native integer, with a possible gap in between. + Specifically, with a 64-bit integer type, each halfword would +be stored right-justified in the two 32-bit subtypes. If its value is +examined as a 64-bit integer, the left-half bits will appear to be shifted up +14 bits away from the right-half bits. Halfwords can thus be readily +manipulated (I bet you didn't realize the PDP-10 depends so heavily on +a halfword architecture!) and certain fullword operations can still be +carried out using the full integer type. Arithmetic operations do require +special care however to compensate for the gap bits. + +(5) USEGCCSPARC: Hybrid; only usable with GCC. + WORD10_USEGCCSPARC = 1 + This is a special version of USEHUN which is necessitated by a +misfeature of SUN SPARC systems that, in turn, requires the GCC +compiler to circumvent. Thanks to the unholy legacy of PCC, the +historical convention for struct/union passing on Sparcs always +requires passing a pointer, regardless of how small the structure +might be. GCC extensions to C include a "long long" type that is +vastly more efficient for this purpose, although it is much slower for +arithmetic in general. GCC also allows in-line functions, which in +this case permit efficient casting of this type to a struct/union type +for most operations. + Hence this model. It uses a "native" 64-bit integer type (ie, +long long) for storing the 36-bit word, but actually manipulates the +data as if it were a structure of two 32-bit halfwords. If its value +were examined as a 64-bit integer, the left-half bits would appear to +be shifted up 14 bits away from the right-half bits. + + +Only one of the above models may be requested. For either of USENAT or +USEINT, a refinement is also possible if the compiler/platform supports +an overlaid subdivision into bitfields: + + WORD10_BITF = 1 + WORD10_BITFBIGEND = 1 or 0 to indicate whether compiler + assigns bitfields from left (big, 1) or right (little, 0) + +There are also auxiliary definitions for double-words and quad-words. +It's possible that some platforms will support integer types big enough +to hold these values (72+ bits), resulting in additional models. +However, no provision is made for this. + +===================>>>>> !!!NOTE!!! <<<<<======================= + + In all models, unused bits MUST BE ZERO!!! + ==== == ==== + +The code always assumes this, in order to avoid the overhead of needless +pre-masking. Anything that modifies the contents of a w10_t must always +ensure that the final result is properly masked. When dealing with +AC or memory words, it is highly advisable to avoid any modification until +the complete result is available for storage, in order to allow for +the possibility of multiprocess or even multiprocessor operation. + +*/ + +#ifndef WORD10_INCLUDED +#define WORD10_INCLUDED 1 + +#ifdef RCSID + RCSID(word10_h,"$Id: word10.h,v 2.4 2001/11/19 10:37:38 klh Exp $") +#endif + +/* Attempt to see what integer type sizes are available. +** This code is not meant to be comprehensive, but should work for +** most compiler/platform combinations. It assumes: +** + Machine is 2s-complement with high bit the sign. +** + Preprocessor can use right-shift (>>) in #if expressions. +** + exists. +** + The _MAX values have all bits 1 except the sign bit. +** +** Note: The size tests are made by comparing _MAX rather than +** U_MAX to avoid stressing the arithmetic capabilities of +** some preprocessors. +** +** Note: ANSI C compilers are required to support an integer datatype +** of at least 32 bits, so this is our worst-case assumption and simplifies +** some tests. 1L<<31 should always be non-zero. +** +** Note: this code tests for the existence of "long long" and uses it if +** necessary. However, the "1LL" and "1ULL" constructs can't be used +** in preprocessor tests because GCC's PP is screwed up and doesn't understand +** its own datatypes! So we just assume that if long long exists, it's +** at least 64 bits in length. +** +** Note finally the peculiar cascaded shifts for testing sizes of more than 32. +** This is because ANSI C does not define the result of a single logical shift +** when the # of bits shifted is >= the # of bits in the type; some +** machines can only shift modulo 32 or 64, thus (a<<32) might be a no-op +** instead of returning 0! Even worse, some do nothing whatsoever if the +** shift count is larger than the word size. A shift of 31 should always +** work, however. +*/ + +#include /* Find platform/compiler limits */ +#include "cenv.h" /* May need CENV_CPU_ defs */ + +#define WORD10_TX_UNK 0 /* Type indices for available native C types */ +#define WORD10_TX_CHAR 1 +#define WORD10_TX_SHORT 2 +#define WORD10_TX_INT 3 +#define WORD10_TX_LONG 4 +#define WORD10_TX_LLONG 5 /* Long long */ + +/* Need a few constants for the following tests */ +#define MASK17 0377777 +#define MASK18 0777777 +#define MASK31 017777777777 + + +#ifndef WORD10_INT18 +# if (SHRT_MAX >= MASK17) +# define WORD10_INT18 short +# define WORD10_ITX18 WORD10_TX_SHORT +# elif (INT_MAX >= MASK17) +# define WORD10_INT18 int +# define WORD10_ITX18 WORD10_TX_INT +# else +# define WORD10_INT18 long +# define WORD10_ITX18 WORD10_TX_LONG +# endif +#endif +#ifndef WORD10_INT19 +# if (INT_MAX >= MASK18) +# define WORD10_INT19 int +# define WORD10_ITX19 WORD10_TX_INT +# else +# define WORD10_INT19 long +# define WORD10_ITX19 WORD10_TX_LONG +# endif +#endif +#ifndef WORD10_INT32 +# if (INT_MAX >= MASK31) +# define WORD10_INT32 int +# define WORD10_ITX32 WORD10_TX_INT +# define WORD10_INT32_MAX INT_MAX +# else +# define WORD10_INT32 long +# define WORD10_ITX32 WORD10_TX_LONG +# define WORD10_INT32_MAX LONG_MAX +# endif +#endif + +/* Note whether our 32-bit type is exactly 32 bits. Sometimes it's bigger. +*/ +#if WORD10_INT32_MAX == MASK31 +# define WORD10_INT32_EXACT 1 +#else +# define WORD10_INT32_EXACT 0 +#endif + + +/* Above 32 life gets more adventurous. +** This code must avoid using any values larger than 32 bits, unless +** LLONG_MAX is defined. In which case we presume that the +** "long long" datatype is finally available and has at least +** the 64 bits it is required to have. +*/ +#ifndef WORD10_INT36 +# if ((INT_MAX >> 4) >= MASK31) /* See if pos int has 36 bits */ +# define WORD10_INT36 int +# define WORD10_ITX36 WORD10_TX_INT +# if ((INT_MAX >> 4) == MASK31) /* See if exactly 36 bits */ +# define WORD10_INT36_EXACT 1 +# endif +# elif ((LONG_MAX >> 4) >= MASK31) /* See if pos long has 36 bits */ +# define WORD10_INT36 long +# define WORD10_ITX36 WORD10_TX_LONG +# if ((LONG_MAX >> 4) == MASK31) /* See if exactly 36 bits */ +# define WORD10_INT36_EXACT 1 +# endif +# elif defined(LLONG_MAX) /* Assume long long has >=64 bits */ +# define WORD10_INT36 long long +# define WORD10_ITX36 WORD10_TX_LLONG +# define WORD10_INT36_EXACT 0 +# endif +#endif +#ifndef WORD10_INT36_EXACT +# define WORD10_INT36_EXACT 0 +#endif + +#ifndef WORD10_INT37 +# if ((INT_MAX >> 5) >= MASK31) /* See if pos int has 36 bits */ +# define WORD10_INT37 int +# define WORD10_ITX37 WORD10_TX_INT +# elif ((LONG_MAX >> 5) >= MASK31) /* See if pos long has 36 bits */ +# define WORD10_INT37 long +# define WORD10_ITX37 WORD10_TX_LONG +# elif defined(LLONG_MAX) /* Assume long long has >=64 bits */ +# define WORD10_INT37 long long +# define WORD10_ITX37 WORD10_TX_LLONG +# endif +#endif + +/* This one is a little trickier; find the smallest type with 64 bits, + * without using values larger than 32 bits. Don't know if it's safe + * to shift more than 32 bits until we try it with 31 first! If the + * first test with a 31-bit shift passes, the type has at least 62 bits. + */ +#ifndef WORD10_INT64 +# if ((INT_MAX >> 31) >= MASK31) && ((INT_MAX >> 33) >= MASK31) +# define WORD10_INT64 int +# define WORD10_ITX64 WORD10_TX_INT +# elif ((LONG_MAX >> 31) >= MASK31) && ((LONG_MAX >> 33) >= MASK31) +# define WORD10_INT64 long +# define WORD10_ITX64 WORD10_TX_LONG +# elif defined(LLONG_MAX) /* Assume long long has 64+ bits */ +# define WORD10_INT64 long long +# define WORD10_ITX64 WORD10_TX_LLONG +# endif +#endif + + +/* This test doesn't work for GCC because after 10 years the preprocessor +** STILL doesn't recognize its own "LL" suffix denoting a long long! +*/ +#if 0 +#ifndef WORD10_INT72 +# if ((LONG_MAX >> 31) >= MASK31) && ((LONG_MAX >> 41) >= MASK31) +# define WORD10_INT72 long +# define WORD10_ITX72 WORD10_TX_LONG +# elif defined(LLONG_MAX) && ((LLONG_MAX >> 31) >= MASK31) \ + && ((LLONG_MAX >> 41) >= MASK31) +# define WORD10_INT72 long long +# define WORD10_ITX72 WORD10_TX_LLONG +# endif +#endif +#endif + +/* Determine what representation model to use for a PDP-10 word. +*/ + +#ifndef WORD10_USEHWD /* Ensure all flags have a defined value */ +# define WORD10_USEHWD 0 +#endif +#ifndef WORD10_USENAT +# define WORD10_USENAT 0 +#endif +#ifndef WORD10_USEINT +# define WORD10_USEINT 0 +#endif +#ifndef WORD10_USEHUN +# define WORD10_USEHUN 0 +#endif +#ifndef WORD10_USEGCCSPARC +# define WORD10_USEGCCSPARC 0 +#endif + +/* If none of the above are set, determine a default. Native +** model is never the default. +*/ +#if !(WORD10_USEHWD|WORD10_USENAT|WORD10_USEINT|WORD10_USEHUN \ + | WORD10_USEGCCSPARC) +# if CENV_CPU_SPARC && defined(__GNUC__) +# undef WORD10_USEGCCSPARC +# define WORD10_USEGCCSPARC 1 +# else +# ifndef WORD10_INT37 /* If big integers not supported, */ +# undef WORD10_USEHWD /* only choice is halfword model */ +# define WORD10_USEHWD 1 +# else /* But if have big integers, */ +# undef WORD10_USEINT /* use simple integer model (or HUN?) */ +# define WORD10_USEINT 1 +# endif +# endif +#endif + + +/* Now set byte and bitfield ordering defs. +** These are completely system-dependent (and can vary even on the +** same platform) but in practice most compilers for a particular +** platform tend to do things the same way, so these defaults will +** generally work. +** WORD10_SMEMBIGEND should be TRUE if structure members within a machine +** word are assigned in left-to-right (big-end) order. +** WORD10_BITFBIGEND should be TRUE if bitfields within a machine +** word are assigned in left-to-right (big-end) order. +** Usually these are the same. +*/ +#ifndef WORD10_SMEMBIGEND +# define WORD10_SMEMBIGEND CENV_CPUF_BIGEND +#endif +#ifndef WORD10_BITFBIGEND +# define WORD10_BITFBIGEND WORD10_SMEMBIGEND +#endif + + +/* Now check out BITF setting. +** The default is not to try bitfields even though they might provide +** more efficiency, since the way they are used is non-portable +** and needs to be verified for each platform. +*/ +#ifndef WORD10_BITF /* Unless explicitly requested, */ +# define WORD10_BITF 0 /* portable default does without bitfields */ +#endif + + +/* Basic integer typedefs */ + +typedef WORD10_INT18 int18; /* Smallest type of >= 18 bits */ +typedef unsigned WORD10_INT18 uint18; +typedef WORD10_INT19 int19; /* Smallest type of >= 19 bits */ +typedef unsigned WORD10_INT19 uint19; +typedef WORD10_INT32 int32; /* Smallest type of >= 32 bits */ +typedef unsigned WORD10_INT32 uint32; +#ifdef WORD10_INT36 + typedef WORD10_INT36 int36; /* Smallest type of >= 36 bits */ + typedef unsigned WORD10_INT36 uint36; +#endif +#ifdef WORD10_INT37 + typedef WORD10_INT37 int37; /* Smallest type of >= 37 bits */ + typedef unsigned WORD10_INT37 uint37; +#endif +#ifdef WORD10_INT64 + typedef WORD10_INT64 int64; /* Smallest type of >= 64 bits */ + typedef unsigned WORD10_INT64 uint64; +#endif +#ifdef WORD10_INT72 + typedef WORD10_INT72 int72; /* Smallest type of >= 72 bits */ + typedef unsigned WORD10_INT72 uint72; +#endif + +/* Fundamental PDP-10 word definitions. +** There is only a single PDP-10 word representation for any actual +** instance of this code. However, there can exist several ways of +** defining this representation in C, each of which might be +** more useful than the others in certain situations. +*/ + +/* Halfword - fundamental PDP-10 storage unit +*/ +#define H10BITS 18 /* Number of bits in PDP-10 halfword */ +#define H10OVFL ((uint19)1<>1))<uwd.lh) +# define W10P_RH(p) (((w10union_t *)(p))->uwd.rh) +# define W10P_LHSET(p,v) (((w10union_t *)(p))->uwd.lh = (v)) +# define W10P_RHSET(p,v) (((w10union_t *)(p))->uwd.rh = (v)) +# define W10P_XSET(p,l,r) (*(p) = w10_ximm(l,r)) + +#elif WORD10_USEHWD +# define W10_XIMM(l,r) --illegal-- +# if WORD10_SMEMBIGEND +# define W10_XINIT(l,r) {(l),(r)} /* Data constant initializer */ +# else +# define W10_XINIT(l,r) {(r),(l)} /* Data constant initializer */ +# endif +# define W10_XSET(w,l,r) (W10_LHSET(w,l),W10_RHSET(w,r)) +# define W10_VAR(w) --illegal-- +# define W10_LHVAR(w) ((w).lh) +# define W10_RHVAR(w) ((w).rh) +# define W10_LH(w) ((w).lh) +# define W10_RH(w) ((w).rh) +# define W10_LHSET(w,v) ((w).lh = (v)) +# define W10_RHSET(w,v) ((w).rh = (v)) +# define W10P_LH(p) ((p)->lh) +# define W10P_RH(p) ((p)->rh) +# define W10P_LHSET(p,v) ((p)->lh = (v)) +# define W10P_RHSET(p,v) ((p)->rh = (v)) +# define W10P_XSET(p,l,r) (W10P_LHSET(p,l), W10P_RHSET(p,r)) + +#elif WORD10_USEHUN || WORD10_BITF +# define W10_XIMM(l,r) (((w10uint_t)(l)<uwd.lh) +# define W10P_RH(p) ((p)->uwd.rh) +# define W10P_LHSET(p,v) ((p)->uwd.lh = (v)) +# define W10P_RHSET(p,v) ((p)->uwd.rh = (v)) +# define W10P_XSET(p,l,r) ((p)->i = W10_XIMM(l,r)) + +#elif WORD10_USEINT || WORD10_USENAT +# define W10_XIMM(l,r) ((((w10uint_t)(l))<>H10BITS)) +# define W10_RH(w) ((uint18)((w)&H10MASK)) +# define W10_LHSET(w,v) W10_XSET((w),(v),W10_RH(w)) +# define W10_RHSET(w,v) ((w) = ((w)&(~(w10uint_t)H10MASK))|(v)) +# define W10P_LH(p) W10_LH(*(p)) +# define W10P_RH(p) W10_RH(*(p)) +# define W10P_LHSET(p,v) W10_LHSET(*(p),(v)) +# define W10P_RHSET(p,v) W10_RHSET(*(p),(v)) +# define W10P_XSET(p,l,r) (*(p) = W10_XIMM(l,r)) +#endif + + +/* Macros for double-words and quad-words. + * These are minimal because code is generally expected to know that + * they are arrays of w10_t (as they must be, to remain compatible with + * memory references) and can simply access the contents directly. + * The initializers are provided as a convenience to help get the + * pesky braces correct. + */ +#define DW10_HI(d) ((d).w[0]) +#define DW10_LO(d) ((d).w[1]) +#define DW10_SET(d,hi,lo) ((d).w[0] = (hi), (d).w[1] = (lo)) +/* #define DW10_INIT(hi,lo) {{hi, lo}} */ +#define DW10_XINIT(h0,h1,h2,h3) {{W10_XINIT(h0,h1),W10_XINIT(h2,h3)}} + +#define QW10_XINIT(h0,h1,h2,h3,l0,l1,l2,l3) \ + {{DW10_XINIT(h0,h1,h2,h3), DW10_XINIT(l0,l1,l2,l3)}} + + +/* Deprecated macros, retained for backward compatibility + */ +#define XWDIMM(l,r) W10_XIMM(l,r) +#define XWDINIT(l,r) W10_XINIT(l,r) +#define XWDSET(w,l,r) W10_XSET(w,l,r) +#define XWDVAL(w) W10_VAR(w) +#define LHVAL(w) W10_LHVAR(w) +#define RHVAL(w) W10_RHVAR(w) +#define LHGET(w) W10_LH(w) +#define RHGET(w) W10_RH(w) +#define LHSET(w,v) W10_LHSET(w,v) +#define RHSET(w,v) W10_RHSET(w,v) +#define LHPGET(p) W10P_LH(p) +#define RHPGET(p) W10P_RH(p) +#define LHPSET(p,v) W10P_LHSET(p,v) +#define RHPSET(p,v) W10P_RHSET(p,v) +#define XWDPSET(p,l,r) W10P_XSET(p,l,r) + +#define W10XWD(l,r) W10_XINIT(l,r) +#define LRHSET(w,l,r) W10_XSET(w,l,r) + +#define HIGET(d) DW10_HI(d) +#define LOGET(d) DW10_LO(d) + +#define DXWDINIT(h0,h1,h2,h3) DW10_XINIT(h0,h1,h2,h3) +#define QXWDINIT(h0,h1,h2,h3,l0,l1,l2,l3) QW10_XINIT(h0,h1,h2,h3,l0,l1,l2,l3) + + +/* Conversions to and from w10_t and native integer types. + * 32-bit conversions are always supported. + * 36-bit conversions are supported only if a large enough integer + * type exists -- WORD10_INT will be defined if so. + * + * All w10_t word arguments are assumed to be correctly formatted, but + * no assumptions are made about the integer arguments; they are + * extended and/or masked as necessary. + */ + +#if 0 +/* Macro pseudo-declarations, for documentary purposes */ + +/* Returns a uint32 containing the low 32 bits of word. + * Only 32 bits are returned even if a uint32 is larger. + */ +uint32 W10_U32(w10_t); + +/* Returns a signed int32 containing the low 32 bits of word taken as a + * 32-bit signed value. This means that bit 1<<31 of the word is + * interpreted as a sign bit and extended to fill the rest of the + * int32. It int32 is exactly 32 bits this returns the same + * bits as W10_U32, but if larger then the higher bits will be + * filled with copies of the sign bit. This is true even if the + * native type could hold the entire 36-bit value! + * It's done this way to ensure the behavior doesn't change if + * the actual size of an int32 changes. + */ +int32 W10_S32(w10_t); + +/* Set word to unsigned 32-bit value, zero high bits of word + * This only uses 32 bits even if an int32 is larger. + */ +void W10_U32SET(w10_t, uint32); + +/* Set word to 32-bit signed value. + * Bit 1<<31 of the int32 is always interpreted as a sign bit and + * used to fill the high 4 bits of the word. This is true even if + * a int32 is larger than 32 bits and has a positive value!!!! + */ +void W10_S32SET(w10_t, int32); + +/* ======================================================================== + * The following macros only exist if WORD10_INT is defined + */ + +/* Returns a w10uint_t containing all 36 bits as an unsigned value. + */ +w10uint_t W10_U36(w10_t); + +/* Returns a w10int_t containing all 36 bits as a signed value. + * If a w10int_t is larger than 36 bits, the high order bits will + * have copies of the word's sign bit (1<<35)! Such a value CANNOT + * be stored directly back into a w10_t. + */ +w10int_t W10_S36(w10_t); + +/* Set word to unsigned 36-bit value. + * The argument is cast to a w10uint_t and any higher-order bits + * (beyond 36) of the value are ignored. + */ +void W10_U36SET(w10_t, w10uint_t); + +/* Set word to 36-bit signed value. + * The difference between this and W10_U36SET is that the + * argument is cast to a w10int_t (not w10uint_t); if it is a signed + * integer of less than 36 bits, its sign is thus automatically + * extended by C. + * At that point only the low-order 36 bits are used, regardless of + * whether the int36 is larger and has a positive or negative value. + */ +void W10_S36SET(w10_t, w10int_t); + +#endif + +#define H10SIGN32 (1<<(17-4)) /* LH sign bit of a 32-bit value */ +#define W10SIGN32 (((uint32)1)<<31) /* Word sign bit of a 32-bit value */ + +/* =================================================================== */ + +/* Internal defs shared by various models that have a halfword structure. + * Do them in one place to reduce duplication errors! + * Unfortunately there seems to be no real way to take advantage of + * a gapped integer; closing and opening the gap is faster with struct members. + */ +#if WORD10_USEHWD || WORD10_USEHUN || WORD10_USEGCCSPARC +# if WORD10_INT32_EXACT +# define W10STRU_U32(w) ((uint32)((((uint32)W10_LH(w))<>H10BITS),((i)&H10MASK)) +# define W10STRU_S32SET(w,i) W10STRU_U32SET((w),((int32)(i))) +# else +# define W10STRU_U32(w) ((uint32)(((((uint32)W10_LH(w))<> H10BITS) & MASK14), \ + (i) & H10MASK) +# define W10STRU_S32SET(w,i) W10_XSET((w), \ + ( (((int32)(i))>>H10BITS) & H10SIGN32) \ + ? ((((int32)(i))>>H10BITS) & H10MASK)|(H10MASK&~(int32)MASK14)\ + : ((((int32)(i))>>H10BITS) & H10MASK),\ + ((i) & H10MASK) ) +# endif /* !WORD10_INT32_EXACT */ +# ifdef WORD10_INT +# define W10STRU_U36(w) ((((w10uint_t)W10_LH(w))<> H10BITS) & H10MASK), \ + ((w10uint_t)(i)) & H10MASK) +# define W10STRU_S36SET(w,i) W10STRU_U36SET((w),(w10int_t)(i)) +# endif /* WORD10_INT */ + +#endif /* WORD10_USEHWD || WORD10_USEHUN || WORD10_USEGCCSPARC */ + +/* =================================================================== */ +/* Struct members (HWD, HUN) or gapped integer (GCCSPARC) */ + +#if WORD10_USEHWD || WORD10_USEHUN || WORD10_USEGCCSPARC +# define W10_U32(w) W10STRU_U32(w) +# define W10_S32(w) W10STRU_S32(w) +# define W10_U32SET(w,i) W10STRU_U32SET(w,i) +# define W10_S32SET(w,i) W10STRU_S32SET(w,i) +# define W10_U36(w) W10STRU_U36(w) +# define W10_S36(w) W10STRU_U36(w) +# define W10_U36SET(w,i) W10STRU_U36SET(w,i) +# define W10_S36SET(w,i) W10STRU_S36SET(w,i) + +/* =================================================================== */ + +#elif WORD10_USENAT /* Native 36-bit, no masking needed */ + /* Assume int32 is not exactly 32 bits */ +# define W10_U32(w) ((uint32)(W10_VAR(w)&MASK32)) +# define W10_S32(w) ((int32) ((W10_VAR(w)&H10SIGN32) \ + ? (W10_VAR(w) | ~((w10int_t)MASK32)) \ + : (W10_VAR(w) & MASK32) )) +# define W10_U32SET(w,i) (W10_VAR(w) = ((uint32)(i))&MASK32) +# define W10_S32SET(w,i) (W10_VAR(w) = (((int32)(i))&W10SIGN32) \ + ? (((w10int_t)(i)) | ~((w10int_t)MASK32)) \ + : (((w10int_t)(i)) & MASK32) ) +# define W10_U36(w) ((w10uint_t)W10_VAR(w)) +# define W10_S36(w) (( w10int_t)W10_VAR(w)) +# define W10_U36SET(w,i) (W10_VAR(w) = (w10uint_t)(i)) +# define W10_S36SET(w,i) (W10_VAR(w) = ( w10int_t)(i)) + +/* =================================================================== */ + +#elif WORD10_USEINT /* Solid integer */ +# if WORD10_INT32_EXACT +# define W10_U32(w) ((uint32)W10_VAR(w)) +# define W10_S32(w) (( int32)W10_VAR(w)) +# define W10_U32SET(w,i) (W10_VAR(w) = (w10uint_t)(uint32)(i)) +# define W10_S32SET(w,i) (W10_VAR(w) = (( w10int_t)( int32)(i))&MASK36) +# else +# define W10_U32(w) ((uint32)(W10_VAR(w)&MASK32)) +# define W10_S32(w) ((int32) ((W10_VAR(w)&W10SIGN32) \ + ? (W10_VAR(w) | ~((w10int_t)MASK32)) \ + : (W10_VAR(w) & MASK32) )) +# define W10_U32SET(w,i) (W10_VAR(w) = (w10uint_t)((uint32)(i))&MASK32) +# define W10_S32SET(w,i) (W10_VAR(w) = ( w10int_t)(((int32)(i))&W10SIGN32) \ + ? ((((w10int_t)(i)) | ~((w10int_t)MASK32)) & MASK36) \ + : (((w10int_t)(i)) & MASK32) ) +# endif /* !WORD10_INT32_EXACT */ + +# define W10_U36(w) ((w10uint_t)W10_VAR(w)) +# define W10_S36(w) (( w10int_t)((W10_VAR(w)&W10SIGN) \ + ? (W10_VAR(w) | ~((w10int_t)MASK36)) \ + : W10_VAR(w))) +# define W10_U36SET(w,i) (W10_VAR(w) = ((w10uint_t)(i))&MASK36) +# define W10_S36SET(w,i) (W10_VAR(w) = (( w10int_t)(i))&MASK36) + +#endif + +#endif /* ifndef WORD10_INCLUDED */ diff --git a/src/wxtest.c b/src/wxtest.c new file mode 100644 index 0000000..3cbd06e --- /dev/null +++ b/src/wxtest.c @@ -0,0 +1,894 @@ +/* WXTEST.C - Test program for word10.h definitions +*/ +/* $Id: wxtest.c,v 2.5 2001/11/19 10:19:55 klh Exp $ +*/ +/* Copyright © 2001 Kenneth L. Harrenstien +** All Rights Reserved +** +** This file is part of the KLH10 Distribution. Use, modification, and +** re-distribution is permitted subject to the terms in the file +** named "LICENSE", which contains the full text of the legal notices +** and should always accompany this Distribution. +** +** This software is provided "AS IS" with NO WARRANTY OF ANY KIND. +** +** This notice (including the copyright and warranty disclaimer) +** must be included in all copies or derivations of this software. +*/ +/* + * $Log: wxtest.c,v $ + * Revision 2.5 2001/11/19 10:19:55 klh + * Solaris port: rename INTMAX_MAX to INTMAX_SMAX + * + * Revision 2.4 2001/11/10 21:28:59 klh + * Final 2.0 distribution checkin + * + */ + +/* + The only purpose of this program is to do some rudimentary testing +of the facilities defined by word10.h to help verify that they are +working properly. Simply running other programs that use them is not +guaranteed to be complete or reliable, and it's time-consuming to do +this for all possible variations. +*/ + +#include +#include +#include + +#include "rcsid.h" +#include "word10.h" + +#ifdef RCSID + RCSID(wxtest_c,"$Id: wxtest.c,v 2.5 2001/11/19 10:19:55 klh Exp $") +#endif + +#ifndef TRUE +# define TRUE 1 +# define FALSE 0 +#endif + +int swprint = 1; +int swverbose = 0; +int nerrors = 0; + +/* Misc apparatus */ + +char usage[] = "\ +Usage: %s -[qvh]\n\ + -q Quiet\n\ + -v Verbose\n\ + -h Help (this stuff)\n"; + +int dotests(void); +int txtest(void); +int tinttypes(void); +int tmasks(void); + +main(int argc, char **argv) +{ + char *cp; + + if (argc > 1) { + if (argv[1][0] != '-') { + fprintf(stderr, usage, argv[0]); + exit(1); + } + for (cp = &argv[1][0]; *++cp; ) switch (*cp) { + case 'q': swprint = 0; break; + case 'v': swprint = swverbose = 1; break; + case 'h': fprintf(stdout, usage, argv[0]); + exit(0); + default: + fprintf(stderr, usage, argv[0]); + exit(1); + } + } + + dotests(); + return nerrors; +} + +/* Test tables & constants */ + +/* Note using "INTMAX_SMAX" instead of "INTMAX_MAX" -- latter conflicts + with a Solaris def +*/ +#if defined(ULLONG_MAX) +# define INTMAX long long +# define INTMAX_NAME "long long" +# define INTMAX_SMAX LLONG_MAX +# define SUFF(v) v ## LL +# define USUFF(v) v ## ULL +#elif defined(ULONG_MAX) +# define INTMAX long +# define INTMAX_NAME "long" +# define INTMAX_SMAX LONG_MAX +# define SUFF(v) v ## L +# define USUFF(v) v ## UL +#else +# define INTMAX int +# define INTMAX_NAME "int" +# define INTMAX_SMAX INT_MAX +# define SUFF(v) v +# define USUFF(v) v +#endif +#define UINTMAX unsigned INTMAX + +#define WXHBITS 18 +#define WXHMASK 0777777 +#define WXPARGS(v) (long)((v) >> WXHBITS), (long)((v) & WXHMASK) + + +struct txent { + char *tx_type; + char *tx_macro; + int tx_val; + int tx_bits; +} txtab[] = { + { "unknown","WORD10_TX_UNK", WORD10_TX_UNK }, /* 0 */ + { "char", "WORD10_TX_CHAR", WORD10_TX_CHAR }, /* 1 */ + { "short", "WORD10_TX_SHORT", WORD10_TX_SHORT }, /* 2 */ + { "int", "WORD10_TX_INT", WORD10_TX_INT }, /* 3 */ + { "long", "WORD10_TX_LONG", WORD10_TX_LONG }, /* 4 */ + { "long long","WORD10_TX_LLONG", WORD10_TX_LLONG }, /* 5 */ + { NULL, NULL, 0 } +}; + + +struct maskent { + char *me_mac; + int me_bits; + UINTMAX me_val; +} masktab[] = { + { "MASK12", 12, MASK12 }, + { "MASK14", 14, MASK14 }, + { "MASK16", 16, MASK16 }, + { "MASK17", 17, MASK17 }, + { "MASK18", 18, MASK18 }, + { "MASK22", 22, MASK22 }, + { "MASK23", 23, MASK23 }, + { "MASK30", 30, MASK30 }, + { "MASK31", 31, MASK31 }, + { "MASK32", 32, MASK32 }, +#ifdef MASK36 + { "MASK36", 36, MASK36 }, +#endif + { "", 0, 0 } +}; + +int +dotests(void) +{ + + if (swprint) { + printf("Default word10 model: %s\n", WORD10_MODEL); + printf("Maximum integer type used: %s\n", INTMAX_NAME); + } + nerrors += txtest(); /* Show native integer types available */ + nerrors += tmasks(); /* Verify masks */ + nerrors += tinttypes(); /* Verify types */ + + nerrors += testfmt(); /* Verify bit sets and identify format */ + nerrors += test32(); /* Verify 32-bit conversions */ +#ifdef WORD10_INT + nerrors += test36(); /* Verify 36-bit conversions */ +#endif + + return nerrors; +} + +/* Verify and set up TX table +*/ +int +txtest(void) +{ + register int i, j; + int nerrs = 0; + + for (i = 0; txtab[i].tx_type; ++i) { + struct txent *t = &txtab[i]; + UINTMAX uv; + + if (t->tx_val != i) { + printf("ITX screwup, entry %d \"%s\" misdefined as %d\n", + i, t->tx_type, t->tx_val); + ++nerrs; + /* Keep going anyway */ + } + + /* Verify whether the type exists and find its exact bit size */ + switch (t->tx_val) { + case WORD10_TX_UNK: continue; + case WORD10_TX_CHAR: uv = (UINTMAX) ((unsigned char)-1); break; + case WORD10_TX_SHORT: uv = (UINTMAX) ((unsigned short)-1); break; + case WORD10_TX_INT: uv = (UINTMAX) ((unsigned int)-1); break; + case WORD10_TX_LONG: uv = (UINTMAX) (~(unsigned long)0); break; + case WORD10_TX_LLONG: +#ifdef ULLONG_MAX + uv = (UINTMAX) ~((unsigned long long)0); +#else + uv = 0; + if (swverbose) + printf("Warning: no support for \"long long\"\n"); +#endif + break; + default: + printf("Type error: %s has unknown WORD10_TX index %d\n", + t->tx_type, t->tx_val); + nerrs++; + continue; + } + /* Find exact size in bits */ + for (j = 0; uv; ++j) + uv >>= 1; + t->tx_bits = j; + if (swverbose) { + printf("Type \"%s\" (%s) has %d bits\n", + t->tx_type, t->tx_macro, t->tx_bits); + } + } + if (swprint) { + printf("Type index test %s\n", + (nerrs ? "Failed" : "Passed")); + } + return nerrs; +} + +/* Verify masks +*/ +int +tmasks(void) +{ + register int i, j; + int nerrs = 0; + + for (i = 0; j = masktab[i].me_bits; ++i) { + UINTMAX mask = 1; + /* Generate mask in a particularly stupid way to ensure that the + clever ways worked. + */ + while (--j > 0) { + mask <<= 1; + mask |= 1; + } + if (swverbose) { + printf("Testing MASK%d %lo,,%lo == %lo,,%lo\n", + masktab[i].me_bits, + WXPARGS(masktab[i].me_val), + WXPARGS(mask)); + } + + if (mask != masktab[i].me_val) { + printf("Mask error: %s defined as %lo,,%lo computed as %lo,,%lo\n", + masktab[i].me_mac, + WXPARGS(masktab[i].me_val), + WXPARGS(mask)); + ++nerrs; + } + } + if (swprint) { + printf("Mask test %s\n", + (nerrs ? "Failed" : "Passed")); + } + return nerrs; +} + + +/* Verify existence of various integer types and their sizes + */ +#ifndef WORD10_INT18 +# error "WORD10_INT18 must be defined" +#endif +#ifndef WORD10_INT19 +# error "WORD10_INT19 must be defined" +#endif +#ifndef WORD10_INT32 +# error "WORD10_INT32 must be defined" +#endif + +struct typent { + char *te_name; + int te_bits; + int te_itx; +} typetab[] = { + { "WORD10_INT18", 18, WORD10_ITX18 }, + { "WORD10_INT19", 19, WORD10_ITX19 }, + { "WORD10_INT32", 32, WORD10_ITX32 }, +#ifdef WORD10_INT36 + { "WORD10_INT36", 36, WORD10_ITX36 }, +#endif +#ifdef WORD10_INT37 + { "WORD10_INT37", 37, WORD10_ITX37 }, +#endif +#ifdef WORD10_INT64 + { "WORD10_INT64", 64, WORD10_ITX64 }, +#endif +#ifdef WORD10_INT72 + { "WORD10_INT72", 72, WORD10_ITX72 }, +#endif +#ifdef WORD10_INT + { "WORD10_INT", 36, WORD10_ITX }, +#endif + { NULL, 0, 0 } +}; + +/* Verify typedefs were set up */ + int18 v_int18 = 0; +uint18 v_uint18 = 0; + int19 v_int19 = 0; +uint19 v_uint19 = 0; + int32 v_int32 = 0; +uint32 v_uint32 = 0; + +#ifdef WORD10_INT36 + int36 v_int36 = 0; +uint36 v_uint36 = 0; +#endif +#ifdef WORD10_INT37 + int37 v_int37 = 0; +uint37 v_uint37 = 0; +#endif +#ifdef WORD10_INT64 + int64 v_int64 = 0; +uint64 v_uint64 = 0; +#endif +#ifdef WORD10_INT72 + int72 v_int72 = 0; +uint72 v_uint72 = 0; +#endif + +#ifdef WORD10_INT + w10int_t v_intw10 = 0; +w10uint_t v_uintw10 = 0; +#endif + +int +tinttypes(void) +{ + register int i, j; + int nerrs = 0; + struct typent *t; + struct txent *tx; + + for (i = 0; typetab[i].te_name; ++i) { + UINTMAX uv; + t = &typetab[i]; + + /* Verify that the type has at least the number of bits claimed */ + switch (t->te_itx) { + case WORD10_TX_CHAR: + case WORD10_TX_SHORT: + case WORD10_TX_INT: + case WORD10_TX_LONG: + case WORD10_TX_LLONG: + if ((tx = &txtab[t->te_itx])->tx_bits < t->te_bits) { + printf("Type error: %s wants %d bits but only has %d\n", + t->te_name, t->te_bits, tx->tx_bits); + ++nerrs; + } else if (swverbose) + printf("Type %s is \"%s\" with %d bits\n", + t->te_name, tx->tx_type, tx->tx_bits); + + break; + default: + printf("Type error: %s has unknown WORD10_TX %d\n", + t->te_name, t->te_itx); + nerrs++; + continue; + } + } + + /* Extra stuff */ +#ifndef WORD10_INT32_EXACT + printf("WORD10_INT32_EXACT is undefined!\n"); + ++nerrs; +#else + if ((WORD10_INT32_EXACT==0) == (txtab[WORD10_ITX32].tx_bits == 32)) { + printf("WORD10_INT32_EXACT incorrect: %s but has %d bits\n", + (WORD10_INT32_EXACT ? "TRUE" : "FALSE"), + txtab[WORD10_ITX32].tx_bits); + ++nerrs; + } else if (swverbose) + printf("WORD10_INT32_EXACT correct: %s (%d bits)\n", + (WORD10_INT32_EXACT ? "TRUE" : "FALSE"), + txtab[WORD10_ITX32].tx_bits); +#endif + +#ifdef WORD10_INT +# ifndef WORD10_INT36_EXACT + printf("WORD10_INT36_EXACT is undefined!\n"); + ++nerrs; +# else + if ((WORD10_INT36_EXACT==0) == (txtab[WORD10_ITX36].tx_bits == 36)) { + printf("WORD10_INT36_EXACT incorrect: %s but has %d bits\n", + (WORD10_INT36_EXACT ? "TRUE" : "FALSE"), + txtab[WORD10_ITX36].tx_bits); + ++nerrs; + } else if (swverbose) + printf("WORD10_INT36_EXACT correct: %s (%d bits)\n", + (WORD10_INT36_EXACT ? "TRUE" : "FALSE"), + txtab[WORD10_ITX36].tx_bits); +# endif +#endif + + if (swprint) { + printf("Type defs test %s\n", + (nerrs ? "Failed" : "Passed")); + } + return nerrs; +} + +/* Test conversions to and from w10_t and native integer types. + */ + +/* TEST THE INTEGER ARGS! + do char and uchar, then INTMAX and UINTMAX. + do all bit positions, plus all-ones and all masks. + */ + +/* Easiest to test 36-bit stuff first, if possible */ + +/* General algorithm for all patterns: + Store pattern, fetch via LH/RH to ensure safe, then fetch + pattern and compare. + */ + +#ifdef WORD10_INT +int +test36(void) +{ + register int i, j; + unsigned char *ucp; + signed char sc; + unsigned char uc; + INTMAX sm; + UINTMAX um; + w10_t w; + int nerrs = 0; + + /* Test zero set */ + memset(&w, -1, sizeof(w)); /* Fill with 1-bits */ + W10_U36SET(w, 0); + if (W10_LH(w) && W10_RH(w)) { + printf("test36: Word clear failed\n"); + ++nerrs; + } + for (i = 0, ucp = (unsigned char *)&w; i < sizeof(w); ++i) { + if (*ucp) { + printf("test36: Excess bits still set after clear\n"); + ++nerrs; + break; + } + } + +#define PATTEST(desc,varsign,var,init,iter,setsign,getsign) \ + for (i = 0, var = init; var; iter, ++i) { \ + UINTMAX umax, vown; \ + if (setsign) \ + W10_S36SET(w, var); \ + else W10_U36SET(w, var); \ + if (getsign) \ + umax = W10_S36(w); \ + else umax = W10_U36(w); \ + /* Do own conversion for check */\ + if (varsign) \ + vown = ((INTMAX)var)&MASK36; \ + else \ + vown = ((UINTMAX)var)&MASK36; \ + if (getsign && (vown & W10SIGN)) \ + vown |= ~(UINTMAX)MASK36; \ + if (vown != umax) { \ + printf("test36: %s %d: %lo,,%lo != %lo,,%lo\n", desc, i, \ + WXPARGS((UINTMAX)vown), WXPARGS(umax)); \ + ++nerrs; \ + break; \ + } \ + } + +#define SI 1 +#define UI 0 + + /* Do 1-bit pattern for un/signed char */ + PATTEST("1-bit uchar U36", UI, uc, 1, uc<<=1, UI, UI) + PATTEST("1-bit schar U36", SI, sc, 1, sc<<=1, UI, UI) + PATTEST("1-bit uchar S36", UI, uc, 1, uc<<=1, SI, SI) + PATTEST("1-bit schar S36", SI, sc, 1, sc<<=1, SI, SI) + /* Do 1-bit pattern for un/signed INTMAX */ + PATTEST("1-bit umax U36", UI, um, 1, um<<=1, UI, UI) + PATTEST("1-bit smax U36", SI, sm, 1, sm<<=1, UI, UI) + PATTEST("1-bit umax S36", UI, um, 1, um<<=1, SI, SI) + PATTEST("1-bit smax S36", SI, sm, 1, sm<<=1, SI, SI) + + /* Do high-mask pattern for un/signed char */ + PATTEST("himask uchar U36", UI, uc, -1, uc<<=1, UI, UI) + PATTEST("himask schar U36", SI, sc, -1, sc<<=1, UI, UI) + PATTEST("himask uchar S36", UI, uc, -1, uc<<=1, SI, SI) + PATTEST("himask schar S36", SI, sc, -1, sc<<=1, SI, SI) + /* Do high-mask pattern for un/signed INTMAX */ + PATTEST("himask umax U36",UI, um,USUFF(-1),um<<=1,UI,UI) + PATTEST("himask smax U36",SI, sm, SUFF(-1),sm<<=1,UI,UI) + PATTEST("himask umax S36",UI, um,USUFF(-1),um<<=1,SI,SI) + PATTEST("himask smax S36",SI, sm, SUFF(-1),sm<<=1,SI,SI) + +#define UCI CHAR_MAX +#define MCI INTMAX_SMAX + + /* Do low-mask pattern for un/signed char */ + PATTEST("lomask uchar U36",UI, uc,UCI,uc>>=1,UI,UI) + PATTEST("lomask schar U36",SI, sc,UCI,sc>>=1,UI,UI) + PATTEST("lomask uchar S36",UI, uc,UCI,uc>>=1,SI,SI) + PATTEST("lomask schar S36",SI, sc,UCI,sc>>=1,SI,SI) + /* Do low-mask pattern for un/signed INTMAX */ + PATTEST("lomask umax U36",UI, um,MCI,um>>=1,UI,UI) + PATTEST("lomask smax U36",SI, sm,MCI,sm>>=1,UI,UI) + PATTEST("lomask umax S36",UI, um,MCI,um>>=1,SI,SI) + PATTEST("lomask smax S36",SI, sm,MCI,sm>>=1,SI,SI) + +#undef UCI +#undef MCI +#undef SI +#undef UI + + if (swprint) { + printf("36-bit conversions test %s\n", + (nerrs ? "Failed" : "Passed")); + } + return nerrs; +} +#endif /* WORD10_INT */ + + +#if 0 + define H10SIGN32 (1<<(17-4)) /* LH sign bit of a 32-bit value */ + define W10SIGN32 (((uint32)1)<<31) /* Word sign bit of a 32-bit value */ +#endif + +int +test32(void) +{ + register int i, j; + unsigned char *ucp; + signed char sc; + unsigned char uc; + INTMAX sm; + UINTMAX um; + w10_t w; + int nerrs = 0; + + /* 32-bit special: verify macros are correct */ + if (H10SIGN32 != 020000) { /* LH sign bit of a 32-bit value */ + printf("H10SIGN32 incorrect: %lo\n", (long)H10SIGN32); + ++nerrs; + } + if (W10SIGN32 != 020000000000L) { /* Word sign bit of a 32-bit value */ + printf("W10SIGN32 incorrect: %lo\n", (long)W10SIGN32); + ++nerrs; + } + + /* Test zero set */ + memset(&w, -1, sizeof(w)); /* Fill with 1-bits */ + W10_U32SET(w, 0); + if (W10_LH(w) && W10_RH(w)) { + printf("test32: Word clear failed\n"); + ++nerrs; + } + for (i = 0, ucp = (unsigned char *)&w; i < sizeof(w); ++i) { + if (*ucp) { + printf("test32: Excess bits still set after clear\n"); + ++nerrs; + break; + } + } + +#undef PATTEST +#define PATTEST(desc,varsign,var,init,iter,setsign,getsign) \ + for (i = 0, var = init; var; iter, ++i) { \ + UINTMAX umax, vown; \ + if (setsign) \ + W10_S32SET(w, var); \ + else W10_U32SET(w, var); \ + if (getsign) \ + umax = W10_S32(w); \ + else umax = W10_U32(w); \ + /* Do own conversion for check */\ + if (varsign) \ + vown = ((INTMAX)var)&MASK32; \ + else \ + vown = ((UINTMAX)var)&MASK32; \ + if (getsign && (vown & W10SIGN32)) \ + vown |= ~(UINTMAX)MASK32; \ + if (vown != umax) { \ + printf("test32: %s %d: %lo,,%lo != %lo,,%lo\n", desc, i, \ + WXPARGS((UINTMAX)vown), WXPARGS(umax)); \ + ++nerrs; \ + break; \ + } \ + } + +#define SI 1 +#define UI 0 + + /* Do 1-bit pattern for un/signed char */ + PATTEST("1-bit uchar U32", UI, uc, 1, uc<<=1, UI, UI) + PATTEST("1-bit schar U32", SI, sc, 1, sc<<=1, UI, UI) + PATTEST("1-bit uchar S32", UI, uc, 1, uc<<=1, SI, SI) + PATTEST("1-bit schar S32", SI, sc, 1, sc<<=1, SI, SI) + /* Do 1-bit pattern for un/signed INTMAX */ + PATTEST("1-bit umax U32", UI, um, 1, um<<=1, UI, UI) + PATTEST("1-bit smax U32", SI, sm, 1, sm<<=1, UI, UI) + PATTEST("1-bit umax S32", UI, um, 1, um<<=1, SI, SI) + PATTEST("1-bit smax S32", SI, sm, 1, sm<<=1, SI, SI) + + /* Do high-mask pattern for un/signed char */ + PATTEST("himask uchar U32", UI, uc, -1, uc<<=1, UI, UI) + PATTEST("himask schar U32", SI, sc, -1, sc<<=1, UI, UI) + PATTEST("himask uchar S32", UI, uc, -1, uc<<=1, SI, SI) + PATTEST("himask schar S32", SI, sc, -1, sc<<=1, SI, SI) + /* Do high-mask pattern for un/signed INTMAX */ + PATTEST("himask umax U32",UI, um,USUFF(-1),um<<=1,UI,UI) + PATTEST("himask smax U32",SI, sm, SUFF(-1),sm<<=1,UI,UI) + PATTEST("himask umax S32",UI, um,USUFF(-1),um<<=1,SI,SI) + PATTEST("himask smax S32",SI, sm, SUFF(-1),sm<<=1,SI,SI) + +#define UCI CHAR_MAX +#define MCI INTMAX_SMAX + + /* Do low-mask pattern for un/signed char */ + PATTEST("lomask uchar U32",UI, uc,UCI,uc>>=1,UI,UI) + PATTEST("lomask schar U32",SI, sc,UCI,sc>>=1,UI,UI) + PATTEST("lomask uchar S32",UI, uc,UCI,uc>>=1,SI,SI) + PATTEST("lomask schar S32",SI, sc,UCI,sc>>=1,SI,SI) + /* Do low-mask pattern for un/signed INTMAX */ + PATTEST("lomask umax U32",UI, um,MCI,um>>=1,UI,UI) + PATTEST("lomask smax U32",SI, sm,MCI,sm>>=1,UI,UI) + PATTEST("lomask umax S32",UI, um,MCI,um>>=1,SI,SI) + PATTEST("lomask smax S32",SI, sm,MCI,sm>>=1,SI,SI) + +#undef UCI +#undef MCI +#undef SI +#undef UI + + if (swprint) { + printf("32-bit conversions test %s\n", + (nerrs ? "Failed" : "Passed")); + } + return nerrs; +} + + +/* Determine and display word format being used */ + + +/* Word format tables */ + +#define B0 36 +unsigned char wfmt_dbw8[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, B0, 1, 2, 3, + 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35 +}; +unsigned char wfmt_dlw8[] = { + 28, 29, 30, 31, 32, 33, 34, 35, + 20, 21, 22, 23, 24, 25, 26, 27, + 12, 13, 14, 15, 16, 17, 18, 19, + 4, 5, 6, 7, 8, 9, 10, 11, + 0, 0, 0, 0, B0, 1, 2, 3, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; +unsigned char wfmt_dbh4[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, B0, 1, + 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35 +}; +unsigned char wfmt_dlh4[] = { + 28, 29, 30, 31, 32, 33, 34, 35, + 20, 21, 22, 23, 24, 25, 26, 27, + 0, 0, 0, 0, 0, 0, 18, 19, + 0, 0, 0, 0, 0, 0, 0, 0, + 10, 11, 12, 13, 14, 15, 16, 17, + 2, 3, 4, 5, 6, 7, 8, 9, + 0, 0, 0, 0, 0, 0, B0, 1, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +struct { + char *wfname; + int wflen; + unsigned char *wfmt; +} wfmts[] = { + { "DBW8", sizeof(wfmt_dbw8), wfmt_dbw8 }, + { "DLW8", sizeof(wfmt_dlw8), wfmt_dlw8 }, + { "DBH4", sizeof(wfmt_dbh4), wfmt_dbh4 }, + { "DLH4", sizeof(wfmt_dlh4), wfmt_dlh4 } +}; + +int +testfmt(void) +{ + int nerrs = 0; + w10_t w; + unsigned char *wcp = (unsigned char *) &w; + int bsize = txtab[WORD10_TX_CHAR].tx_bits; + int wsize = sizeof(w10_t); + int barrsize = bsize * wsize; + unsigned char *bitarr = (unsigned char *)calloc(1, (size_t)barrsize); + register int i, b, bit; + + if (!barrsize || !bitarr) { + printf("testfmt setup failed!\n"); + return 1; + } + + /* First set each bit in word, then see where it's located in terms + of byte & bit offset. + */ + for (bit = 0; bit < 36; ++bit) { + memset((void *)&w, 0, sizeof(w)); /* Clear word */ + + /* Set single bit in word */ + i = 35-bit; + if (i < 18) + W10_RHSET(w, (1L << i)); + else + W10_LHSET(w, (1L << (i-18))); + + /* Find bit in word via byte access, and make note in bitarr + table of its location. + */ + for (i = 0; i < wsize; ++i) + for (b = 0; b < bsize; ++b) { + if (wcp[i] & (1 << ((bsize-1)-b))) { + /* Found it! */ + if (bitarr[(i*bsize)+b]) { + printf("testfmt conflict! i=%d b=%d old=%d new=%d\n", + i, b, bitarr[(i*bsize)+b] % 36, bit); + ++nerrs; + } + /* Store 0 as B0 */ + bitarr[(i*bsize)+b] = (bit ? bit : B0); + } + } + } + + /* All 36 bits located, now attempt to identify the format + and perhaps display it. + */ + for (i = (sizeof(wfmts)/sizeof(wfmts[0])); --i >= 0;) { + if ((wfmts[i].wflen == barrsize) + && (memcmp(wfmts[i].wfmt, bitarr, barrsize) == 0)) + break; + } + if (i < 0) { + ++nerrs; + printf("testfmt cannot identify internal format\n"); + } else + printf("Internal w10_t format: %s\n", wfmts[i].wfname); + + if ((i < 0) || swverbose) { + printf("Internal w10_t format: %d bytes, %d bits/byte\n", + wsize, bsize); + for (i = 0; i < wsize; ++i) { + printf(" %d: ", i); + for (b = 0; b < bsize; ++b) { + bit = bitarr[(i*bsize)+b]; + if (bit) + printf(" %2d", (bit==B0) ? 0 : bit); + else printf(" z"); + } + printf("\n"); + } + } + + free(bitarr); + if (swprint) { + printf("Format test %s\n", + (nerrs ? "Failed" : "Passed")); + } + return nerrs; +} + +#undef B0 + + +#if 0 /* Stuff noted for possible future testing */ + + +/* Determine representation model in effect */ + + ifndef WORD10_USEHWD + ifndef WORD10_USENAT + ifndef WORD10_USEINT + ifndef WORD10_USEHUN + ifndef WORD10_USEGCCSPARC + + ifndef WORD10_SMEMBIGEND + ifndef WORD10_BITFBIGEND + ifndef WORD10_BITF /* Unless explicitly requested, */ + + +/* Fundamental PDP-10 word definitions. */ + +/* Halfword - fundamental PDP-10 storage unit */ + define H10BITS 18 /* Number of bits in PDP-10 halfword */ + define H10OVFL ((uint19)1<>1))<uwd.lh) + define W10P_RH(p) (((w10union_t *)(p))->uwd.rh) + define W10P_LHSET(p,v) (((w10union_t *)(p))->uwd.lh = (v)) + define W10P_RHSET(p,v) (((w10union_t *)(p))->uwd.rh = (v)) + define W10P_XSET(p,l,r) (*(p) = w10_ximm(l,r)) + +/* Check these? At least the initializers. */ + define DW10_HI(d) ((d).w[0]) + define DW10_LO(d) ((d).w[1]) + define DW10_SET(d,hi,lo) ((d).w[0] = (hi), (d).w[1] = (lo)) +/* define DW10_INIT(hi,lo) {{hi, lo}} */ + define DW10_XINIT(h0,h1,h2,h3) {{W10_XINIT(h0,h1),W10_XINIT(h2,h3)}} + + define QW10_XINIT(h0,h1,h2,h3,l0,l1,l2,l3) \ + {{DW10_XINIT(h0,h1,h2,h3), DW10_XINIT(l0,l1,l2,l3)}} + +#endif /* 0 */