Full Width [alt+shift+f] Shortcuts [alt+shift+k]
Sign Up [alt+shift+s] Log In [alt+shift+l]
1
Australians! They walk among us! Do not be deceived by those charming faces! precious bodily fluids, we must remove the scurrilous larrikin influence of Australia upon our American home computers, and I know exactly where to start! Why allow Dick Smith's name (even though he'd already sold Dick Smith Electronics' controlling interest to Woolies by then but stop ruining my intro) to corrupt this, um, rubber-keyed diminutive beige home computer when we can return it to its prior, pristine, plasticky state as it once rolled out from a glorious Asian factory? Then, to avoid wrecking it with a botched logic board repair, we'll bolt on a USB serial port using the very latest peripherals from down under, write cycle-counted Z80 assembly language to blast data to it at 57.6kbps, and hack a few games. Because only this will make the VZ200 great again! TI vs. Everybody"), was a seismic event in computing. Hong Kong entrepreneurs Allan Wong and Stephen Leung were two of many to realize that the microchip would trigger a revolution in consumer electronics, and over several years accumulated sufficient funding to establish Video Technology Ltd in 1976. Their first factory operated from the Freder Centre in Ma Tau Kok, a semi-industrial area on the west side of Kowloon Bay. Initially VTech, as it became known, concentrated on video games, primarily as an OEM for the more profitable North American and European markets. Their first products were Pong clones, released in the United Kingdom as the Grandstand Adman T.V. Game 2000 (black and white) and Adman T.V. Game 3000 (colour) in 1977, both based on the Texas Instruments TMS1965N, TI's clone of the well-known General Instrument AY-3-8500 Pong-on-a-chip (compare with the rather more complex MOS 7601). Grandstand was a brand name of Adam Imports, then a substantial toy and games importer to the UK and for a period of time New Zealand, and had other Asian contacts, notably Tomy. If the picture on VTech's history page is to...
3 days ago

Stay updated

Get a weekly newsletter with the top 5 articles worth reading every week.

More from Old Vintage Computing Research

MkLinux and the pimped-out Apple Workgroup Server 9150

As is typical here in the Floodgap lab, it all started so innocently: rebuilding a flaky Apple Workgroup Server 9150, the odd duck of the Workgroup Server line and older cousin to our beloved Apple Network Server. And then I just had to pimp it out for MkLinux. sui generis IBM AIX-based Apple Network Server, one of our favourite machines here at Floodgap, there were the WGSes, the Workgroup Servers, Apple's well-intentioned but conflictingly received line of Macintosh rebadges hopped up with high-spec options and special server software. While certain users had long repurposed desktop Macs as ad-hoc servers, these computers were the first Apple systems explicitly positioned and sold as such, and the first generation was even advertised with A/UX, Apple's own hybrid System V UNIX implementation. Rebuilding the Green Giant Before we get into that, however, I've previously talked at length about Apple's early 1990s server strategy as it pertained to how the Apple Network Server ended up running IBM AIX, though not much about why CEO John Sculley's Apple embarked on a server line in the first place. Likewise, we've said very little about the parallel evolution of the Workgroup Servers and their relationship to the core Macintosh product line. Let's consider those topics now. As an upstart in the age of microcomputers, Apple never had a history of making big iron, and the emergence of its server line can actually be traced back almost directly to the failed Macintosh Office concept. In January 1985, Apple's infamous "Lemmings" ad led its new hardware and peripherals announcement, including the AppleTalk Personal Network (what we now call LocalTalk) to set up multidrop serial links between computers and networked devices, the new LaserWriter printer, and a revamped Lisa 2/10 with more RAM and hard disk space newly subsumed into the Mac fold as the Macintosh XL. For a not completely eye-watering amount of money, Macintoshes could talk amongst themselves and send jobs to a high-quality shared printer, with network file storage on the XL's 10MB "Widget" hard disk to come shortly and support for suitably equipped PCs to follow. Steve Jobs, then both chairman of the board and veep of the Macintosh division, confidently predicted 10,000 Macintosh Office networks by the end of the year. While the LaserWriter started at $6995 [$21,750 in 2026 dollars], the refreshed Lisa, er, Macintosh XL now started at a surprisingly competitive $3995 [$12,450], some $6000 less. However, the dirty little secret was that the Macintosh XL was only supposed to be a stopgap, a way to both slowly wind down the hardware and also buy time pending the real Macintosh Office centrepiece: Jobs' new leap forward, the "Big Mac." Big Mac had many vague ideas associated with it, though most of them converged on it being a file server or high-end workstation running some sort of Unix, possibly with the Macintosh interface layered on top. As such, being the most powerful machine in the constellation, it couldn't help but be fated for a central role in the new Macintosh Office. Later in design it was also internally known as the "3M" project, because it would generate at least a megapixel display (its most famous surviving mockup even put it in portrait orientation), provide at least a megabyte of memory, and run at least a million instructions per second on the new 68020. Unfortunately for John Sculley, the company grossly underestimated the XL's appeal at its new lower price. What Apple expected to sell in eighteen months sold in three, emptying the parts inventory so rapidly that Sculley was forced to end its availability in April with Big Mac and its software still stuck in development. ("Apple's strategy may have been too good," mused InfoWorld.) Neither could Apple keep up with demand for the LaserWriter, becoming deeply backordered from manufacturing delays and only shipping 2,500 printers by June. Third-party products ended up filling the gaps, including networking hardware from 3Com and the Centram TOPS file sharing suite. Meanwhile, the Apple board, increasingly concerned about Jobs' excesses in his dual role, ordered Sculley to contain him, after which Jobs was cashiered in May and departed in September. Now in practical ruins, even though Apple promised all shipping products would remain available, the Macintosh Office initiative was officially shut down the same month. sensu stricto was so identified with Jobs internally that it became a political liability in the wake of his precipitous exit. For his part, new Mac product manager Jean-Louis Gassée considered it a "toy" and wasted little time officially canning it, instead promoting the Milwaukee project which had been quietly launched without Jobs' knowledge to create the future Macintosh II. Consequently, Apple's image began to suffer with corporate consumers as they lost confidence in the company's suitability for large office deployments. Sculley addressed this perception head-on at the 1986 introduction of the Macintosh Plus, telling attendees, "I know the real commitment from business customers must be earned by meeting customer needs, by living up to expectations and by keeping promises. We intend to do all of that." The legacy of Big Mac nevertheless persisted in the Macintosh II's development, which at one point was even codenamed "Little Big Mac," and AppleShare's tardy arrival in January 1987 finally made a basic server platform possible. At launch AppleShare was targeted at any Macintosh Plus with sufficient external storage, though in initial versions a machine chosen for server duty had to be all but completely dedicated to the task. Introduced in March, the Mac II, compatible with the new AppleShare as well, would subsequently go on to largely achieve the 3M project's aims. System 7's impending development continued in the background, Apple added X11 support as an option in part to address these and other deficiencies in the graphical interface. "Our first goal was to do a solid Unix," argued Michael J. Homer, technical markets director. A/UX 2.0, introduced in 1990 before System 7's rollout, made good on the majority of Apple's promises: most 32-bit clean applications could now run under a new compatibility layer, MultiFinder was supported, and Macintosh, command line and X11 applications were finally able to coexist on one screen. It was exceptionally well-received by reviewers and users alike, with MacUser approvingly calling it "the most interesting and impressive software to have come out of Apple since HyperCard." Subsequently in July 1991, after System 7's May launch, Apple and IBM announced their new partnership around the PowerPC and a future AIX incorporating the Mac Toolbox. In November this idea was broadened into the future A/UX 4.0, with Apple introducing the System 7-based A/UX 3.0 at the same time. (Another, less-well-known alternative also emerged around this period, but we'll talk about that later when we discuss the history of MkLinux.) hardware, and Macs pressed into server duty in those days were otherwise just Macs. One I particularly remember as an undergraduate at the University of California San Diego was an SE/30 (userserve.ucsd.edu) down in the AP&M B337 lab, running AppleShare version something-or-other and an unknown Gopher server that we all accessed for software resources. I was fortunately able to save its contents before it was decommissioned, since it could still be accessed off-campus at the time. As such, Apple management concluded their persistent lack of a turnkey server configuration was harming additional institutional uptake. At Mactivity '92, the Enterprise Services Division (what would become Apple's Server Group) quizzed attendees for desired features; respondents nearly unanimously favoured high-end hardware on a Unix platform. Fortunately, Apple now credibly had one. In March 1993 the company announced the first Macintoshes to officially be called servers: the Workgroup Server 60, Workgroup Server 80 (collectively codenamed "Blugu") and Workgroup Server 95 ("Chinook"). came with A/UX 3.0.1, a requirement of the included high-performance PDS SCSI and L2 cache (128K to 512K) card, plus an optional 200-seat AppleShare Pro license for file and print services (a separate configuration targeted databases, with Oracle 7 specifically mentioned). In fact, when the demo machine reportedly got stolen shortly before Mactivity93, product manager Marv Su had to "make one" out of a Quadra 950 and a spare card to show to attendees. An optional tray could carry up to five internal hard disks, sitting on top of the system's drive shelf. While the AWS 95 could still run System 7, the PDS SCSI/L2 card was only fully supported in A/UX, and blocked one of the five NuBus slots when installed. The other two systems came with a 50-seat AppleShare 4.0 license and System 7.1, and both the AWS 95 and the AWS 80 had optional built-in DDS (DAT) tape backup. The 60 and 80 launched two months after the 95 in June; Apple started the three systems at $3079, $6399 and $7589 [$7150, $14,850 and $17,600] respectively. The most expensive AWS 95 loadout had 48MB of RAM, a 230MB and a 1GB disk drive, DDS-1 tape and 512K of L2 cache, and sold for $12,929 [$30,000]. itself was the operating system. This port was variously codenamed "Wormhole" and/or "Deep Space Nine," borrowing code from the existing IBM AIX port of Portable NetWare — AIX was PowerOpen too, after all — but modified to run via System 7; Wormhole would then run on the new high-end server, codenamed "Green Giant," using the fastest PowerPC 601 then available in a Quadra 950-style case. Novell NetWare was, of course, the premier network operating system of the early 1990s, but many believed it was already on a slow decline, and the overwhelmingly negative reaction Wormhole provoked from testers who still preferred Unix caught the Enterprise Services Division off guard. Spindler remained doggedly convinced NetWare was the way forward, but as it was imperative the PowerPC server reach market with or shortly after the desktop Power Macintosh, at least initially Green Giant would have to be System 7 — any other options could come later. (As we'll discuss.) 2GB drives and 24MB of RAM. You could also get a 9150 logic board to install in a Quadra 900 or Quadra 950 or, for that matter, an AWS 95, and it would fit your physical case if not your use case (if you were using A/UX). The rebuild came when it started getting flaky last year and intermittently crashing, but the RAM tested good and replacing the boot disk didn't make it better. Since these early 601s can run a little warm, I next redid the heat grease with proper modern compound to see if that would help, and it didn't. While my personal experience has been that these systems don't universally have the bad capacitors that '030 Macintoshes and their contemporaries (e.g., the Macintosh Portable) do, others have reported bad caps on their own early Power Macs, so that was the last thing to try. I sent the board off to Garrett Bunge for a professional recap, who reported there might have been a tiny bit of leakage, but either way it came back looking great. We'll come back to the history when we get to our choices of operating system. Let's first get it back together, starting with the bare case. the PowerBook 1400. this post explains how Apple's serial numbers worked at the time). additional 50-pin header at J9 with no markings. This port is yet another holdover from the Quadra 900, the first Macintosh to implement a separate internal SCSI bus, and later duplicated on the Q950 and AWS 95. On these machines the internal SCSI is a separate second bus from the external SCSI, but there are internal headers for both busses so that internal devices can be on either bus. The 9150 still has this feature and thus still has a port in the same place to handle upgrading machine configurations that need it, but it was officially undocumented and its use otherwise discouraged, and any internal devices connected to J9 will be on the slower CURIO. We won't be using it here. some sort of upgrade pathway, either through their CPU slot or (in this case) as a PDS card, with the notorious exception of the Power Macintosh 7200, 8200 and Workgroup Server 7250 for which Sonnet eventually produced a PCI card to carry a G3. with nothing installed at all. In fact, according to Apple's own tech note, the PDS terminator was only ever intended and shipped with the WGS 8150 and 9150, and it cautions these systems will not work without one if the Processor Direct Slot is empty. the musical sting! (These Power Macs do not make a Mac "bong.") I then got out my customized Mac OS 8.6 boot CD — I prefer 8.6 on Macs on beige pre-G3 Macs without an upgrade card — and put it in the optical drive, and got a Happy Mac! But was it stable? Let's burn it in before we go further with this process. Power Mac 6500 has a 1MB cache, I left it there because that machine is intended primarily as a BeOS box and BeOS doesn't support L2 G3 upgrades, whereas we might be able to dual boot with a G3 in this one even if MkLinux won't support it. (Note from the future: this can work.) not to mess around with the NuBus, PDS or cache slots without unplugging the machine, even if the key is turned to off — there's apparently some trickle power even in the fully-off key position. With the plug out I switched (with difficulty) the ROM into the top slot and put one of the 256K cache sticks in the bottom one. For storage, next we'll prep the ZuluSCSI. I got out a new SD card and created three disk images: a 4GB disk image exclusively for Mac OS (as HFS+), a 4GB disk image for MkLinux (as ext2), and a 1GB image to be partitioned into MkLinux swap and an HFS (not HFS+) exchange partition, since the HFS toolkit included with MkLinux only understands original HFS. We'll need this exchange partition for building custom kernels, since the kernel is booted from the Mac OS side. I chose to make smaller individual devices rather than carving up a larger disk into LUNs simply for ease of organization; the SCSI emulator doesn't really care one way or the other, and I can also separately back the image files up. MkLinux, or, Linux at Mach 3 At this point we'll configure the machine to dual-boot MkLinux, so let's talk some more backstory before we do. the NuBus-compatible version has survived. Because of the age of its System file, the 9150 ironically can't boot from this only known build. (Photo credit, Joe Pugliese, Associated Press.) arresting crimson prototype board complete with populated debug header, additional test points and a silkscreened train logo.) All three PowerPC Workgroup Servers now came with 16MB of base RAM plus System 7.5 and AppleShare 4.1, the 6150 and 8150 got smaller speed bumps of their own to 66MHz and 110MHz, and Apple's software RAID remained supported. software strategy was becoming. PowerOpen-A/UX 4-AIX and Taligent-Pink were still shambling around Cupertino development hell, causing Apple in May 1994 to embark upon an alternative stepwise approach in the form of Copland. Meanwhile, despite claims of Cyberpunk's continued progress, at the April 1995 refresh Spindler had little other choice than to double down on then-current Mac OS with the Apple Internet Server Solution. AISS had the distinct stench of a product assembled under duress, nothing more than a cobbled-together bundle of largely pre-existing software shipped with your choice of Workgroup Server, though the software would of course run on any Power Mac. Other than MacDNS, which wasn't even finished when AISS was launched, plus AppleSearch and "player" versions of HyperCard and FileMaker Pro, everything else was third-party: WebSTAR for server software, Bare Bones' BBEdit as an HTML editor, Adobe Acrobat Pro as, uh, Adobe Acrobat, Netscape Navigator and Everywhere Butler SQL. Apple threw in CGI support for AppleSearch and templates for web pages to be customized. AISS' spec sheet promised even the wimpiest 6150 could handle 3,000 to 5,000 connections per hour (how cute!) and more still was possible by using MacDNS for round-robin load balancing against multiple redundant Workgroup Servers. However, there was one Workgroup Server that couldn't run AISS: the AWS 95. Even as Shiner was supposed to be Apple's next big Unix box, the company seemed to outright repudiate its previous Unix efforts, explicitly claiming that "you don't need to be a Unix nerd" to use a Mac server. The AWS 95 was quietly cancelled in October 1995 at nearly the same time as the collapse of Cyberpunk, marking the end of Apple's inexplicable flirtation with NetWare. With the recognition that A/UX 4 might never ship, Shiner was retrofitted to run standard AIX 4.1 with Apple's extensions, yielding our beloved Apple Network Server in January 1996. Unfortunately for Spindler, Apple's finances were thoroughly in the weeds by then, and his announcement of further staff and model cuts was too much for shareholders who hastily replaced him with Gil Amelio in February. However, the Network Server, defiantly no Macintosh, turned out to be not the only Unixy thing at Apple then either. In 1989 Apple bought out Coral Software in Cambridge, MA (not Corel), developer of Allegro Common Lisp for the Mac, as part of an initiative for new programming paradigms on the Macintosh but also the Newton PDA. The deal involved Coral's team joining Apple's Advanced Technology Group at a new ATG lab to be established there, and Ike Nassi, a veteran engineer and scientist at DEC and part of the initial team at Encore Computer Corporation, was recruited to Apple by then-Newton chief Larry Tesler to run it. Initially Nassi managed the Dylan programming language (at the time written in Macintosh Common Lisp and originally intended for, but never used by, the Newton) and Sculley's Knowledge Navigator passion project before Apple software VP Dave Nagel asked him to move west in 1993 to assist with the software division. Nassi started as VP of development tools, his portfolio notably containing MacApp and the Macintosh Programmers' Workshop, but less than a year later was asked to take over the operating system group. As the PowerPC transition progressed, Nassi became concerned by efforts to build the Common Hardware Reference Platform, believing it would stunt the growth of the Power Macintosh, and positively alarmed by the growing debacle around Copland's infamous NuKernel core, which he said later to the Computer History Museum "was just not going to fly." pairs of National Semiconductor NS32032-family processors. (At my first job out of college, the HP 9000-K250 I was hired to work on had recently replaced what I think was a Multimax.) In fact, Apple already had experience with the Mach microkernel in the form of MacMach, a 1991 CMU research prototoype which ran 68K System 7 as a virtualized task under Mach parallel with its 4.3BSD-derived operating system core — not unlike Classic under Mac OS X many years later, and not to be confused with MachTen, a separate commercial effort from Tenon Intersystems developed independently from CMU or Apple, which runs Mach as a task under Mac OS. MacMach was the product of a joint effort to allow programs from CMU's Andrew Project distributed environment to run directly on the Macintosh II, and Apple provided both financial support and source code from System 7 to aid in development. Notwithstanding the impressive technology, however, MacMach's potential was unavoidably limited. Its reliance on the 4.3BSD codebase incurred the strict requirement that users hold a valid AT&T Unix source code license, all but ensuring it could never be a plausible alternative, let alone threat, to A/UX; and like A/UX, it was never directly ported to PowerPC, condemning it to obsolescence. In the time since, Nagel had moved to VP of engineering; Nassi, who succeeded him as head of the software division, convinced Nagel in August 1995 that resurrecting the concept might pay off. The idea was controversial within Apple, especially with Macintosh division head Howard Lee, who believed it would maroon the Macintosh if IBM considered Mach's highly portable nature as evidence Apple wasn't committed to PowerPC. Lee's fear was not without reason: Spindler previously had to cancel the System 7 port to Intel (i.e., the Star Trek project, instigated by Novell) as a potential threat to the alliance, and of course Project Marklar much later directly led to Apple indeed leaving PowerPC in 2006. Becaue of the internal political ramifications, the PowerPC Mach group initially had few staff resources and operated in near-secrecy for many months. Brett Halle, then manager of the kernel team, suggested to Nassi that a Linux port, modifying the Linux kernel to run as a task under Mach, could demonstrate the microkernel's viability. It even had an obvious name: MkLinux. With Nassi's blessing, Halle quietly sponsored a small team from the Open Software Foundation's Grenoble, France office to begin both the Mach port work and the Linux kernel conversion to run under it. OSF had extensive experience with Mach dating back to their 1989 use of Mach 2.5 as the basis of OSF/1 ("osfmk," intended also as part of the foundation of A/UX 4.0) but this team operated separately, later with a single part-time Apple engineer. The host machines used for initial development were both x86 PCs running regular Linux and HP 9000/700 PA-RISC workstations with OSF/1, with the x86 hosts migrated to an MkLinux port of their own as a self-hosting alpha test. PowerPC objects were cross-compiled using gcc 2.7.1, with cross-platform debugging with gdb over a serial port. For simplicity of development, the Linux task ran under OSF MK as a single monolithic server, though the developers admitted this wouldn't be an ideal system design for a Mach-based platform. The group's rapid progress impressed Nassi, and shortly before the first Developer Release in May 1996 he made the effort official as Apple's "Leveraged Technologies Group." Using the new kernels with tools and pieces previously ported by Thomas and others on top of a modified Red Hat distro, MkLinux DR1 CDs were finished, pressed and handed out, complete with source code, to WWDC attendees that year. The LTG represented Apple's first official attempt to support an open-source software project, and within six months had increased to seven members, five at Apple and two at OSF. An important consequence of its isolated development, however, was its limited hardware support. Apple had since moved from NuBus to PCI with the introduction of the Power Macintosh 9500 in June 1995, and the Workgroup Server line subsequently followed suit. In February 1996 Apple introduced the Workgroup Server 7250 (derived from the Power Macintosh 7200), using the same 120MHz 601+ as the 9150/120, and the first Workgroup Server to use a 604, the 132MHz Workgroup Server 8550 (from the Power Macintosh 8500), along with an updated AISS 2.0 — at which point the 6150, 8150 and 9150 were all discontinued, though the new AISS would still run on them. But, because the LTG had only developed on NuBus Macs, DR1 only officially ran on the 6100, 7100, and 8100 (plus Power Computing's clone Power 100 family), all of which had also been discontinued by the time DR1 emerged. Not even the NuBus PowerPC Workgroup Servers officially appeared in the support list, though of course it would run on them too. As the distribution files were large downloads for the era, partner Prime Time Freeware produced pressed CD-ROMs for a nominal cost. replaced by DR2 in September 1996, primarily a bug-fix and performance release, which in turn was used to supplement an early direct port of the Linux kernel (then called linux-pmac) to PCI Power Macs. For his part, Nassi was pleased with MkLinux's promising launch. However, without a compatibility pathway it could not be an obvious successor to Mac OS, and while Nassi had plans for running the Mac OS under this "PowerPC MacMach" as 68K MacMach did, that goal was never achieved. Nassi quietly disagreed with Gil Amelio's pursuit of Jean-Louis Gassée's BeOS, believing Mach to be the superior platform, and left Apple in November 1996 before Apple's acquisition of Steve Jobs' NeXT — itself of course powered by a Mach-based OS, as is its descendant, modern macOS. MkLinux nevertheless progressed further at Apple after his departure. DR2.1 was announced in March 1997 as the first release to itself run on both NuBus and PCI systems, supporting the 601 and 604 CPUs in the Power Macintosh 6100, 7100, 8100 and 7200, 7500, 7600, 8200, 8500, and 9500. Prime Time duly released CDs of these as well and Apple dubbed 2.1 the "Reference Release," sponsoring Prime Time and editor Rich Morin to release a book-and-CD kit with the tree from January 1997, just before the official announcement. The x86 and PA-RISC ports were also made available (since they already existed), though of course OSF MK could run on many more architectures than that. On the other hand, the Apple Network Server, at the time Apple's only true-Unix machines, never ran it. Cyberdog and OpenDoc and terminate further development of the Network Server, and MkLinux was just as vulnerable because in Jobs' mind it had already achieved its maximal corporate utility: OSF and the LTG had done most of the work porting the microkernel, and with NeXTSTEP's ready BSD foundation Apple had no further need of Linux. Apple disbanded the LTG and transferred control to the community MkLinux Developers Association in 1998 for the release of DR3 in July, by which point MkLinux could run on nearly every four-digit Power Macintosh (now explicitly including the Workgroup Servers) and even early beige G3 models, plus the PowerBook 3400 and the Kanga PowerBook G3. (Apple had since abandoned the 620, which had become hot, expensive, and increasingly surpassed by the cheaper 604 family. Notably, although the 750/G3 completely outclassed it as well, the G3 did so with some features introduced in the 620 like backside L2 cache.) In those days the website was reportedly even hosted on an 80MHz 7100 for some period of time, and allegedly other PowerBooks like the 5300 could boot it also. Mac-specific device support lagged, however, and the project's lack of resources combined with the continual need for Mach-specific changes to the Linux kernel slowed progress further during Apple's transition to the New World Mac. R1 was released in December 1999 and the final release, "pre-R2" (but basically R2 and that's what I'm going to call it), was closed out in August 2002. Although the MkLinux website remains up and accessible today, no new official work on it was subsequently done, and the direct LinuxPPC port replaced it as the de facto Linux basis for Power Macintoshes and the Network Server. Given that complicated tale you might well wonder why we're even bothering with MkLinux at all, especially since non-Mach Linux also eventually supported NuBus Power Macs, including the 9150. The reason is simply historicity: it was the only official "Apple Linux," the only Linux distribution Apple had a direct hand in developing, the only Unixy thing of any kind Apple even vaguely supported on any PowerPC Workgroup Server, and for years was the only Linux that could run on the NuBus ones. With the weight of that behind it and me being a history nerd first and foremost, I never considered running anything else. Before Brinton's freakout and rebuild, I had MkLinux "pre-R2" on it, so now that we're starting fresh we'll hopefully learn from those mistakes I made the first time (at least, if I can remember them). /home and one for everything else in /. Since we want to do this precisely, we'll click Custom. from the Internet Archive or a Garden of other Macintosh sites. The R2RC5 CD also contains components for the contemporary release of LinuxPPC, but it doesn't run on the 9150, so we will not discuss it further in this article. MkLinux-install folder are multiple pieces to copy to your main Mac OS volume's system folder (to the Control Panels, Extensions and Preferences folders). A booter extension (named, oddly enough, MkLinux Booter) is used to launch the Mach kernel portion before the rest of Mac OS loads. This kernel is stored on the Mac filesystem, not the Linux one. The MkLinux control panel is used to pick the boot default, either Linux or Mac OS. The MkLinux Booter extension understands LILO configuration files and one is provided. Open lilo.conf from your Preferences folder after you've copied it. Alternatively, you can click the Custom... button in the MkLinux CDEV. rootdev=/dev/scd0 line is uncommented, and no other rootdev= lines are specified. If you have multiple possible CD-ROM drives or emulated CDs available, you may need to change this number. The other important part of this file is the mach_options= line, down at the bottom. These are the command line arguments passed to Mach. Despite the -v option shown there prominently, which would be expected to trigger a verbose boot, pretty much any boot is verbose in MkLinux. We'll have another use for this field when we get to our upgrades. scri (technically a WorldScript extension, but otherwise loadable as a "regular" extension), not INIT, which causes it to load before other INITs and CDEVs without resorting to trickery like putting a whole lot of spaces before the name. BootX uses the same method. The window here is as close to an About box as you'll get for MkLinux. The staff list appears to date from just before the disbanding of the LTG: from Apple, Brett Halle, Michael Burg, Vicki Brown, Gilbert Coville and Eryk Vershen, and from OSF Research Institute, Nick Stephen, F. Barbou des Places, Éamonn McManus and Gary Thomas. Notice that in this screen booting MkLinux is the default. The default choice is set in the MkLinux control panel. Regardless of what the default actually is, press RETURN to accept it, and ESC to accept the other option. COLOR line when specifying the video console, and compare it with this: COLOR line appears, with the same colours in the same order, and almost certainly descends from the same block of code. Apple implied as much in the December 2000 Kernel Environment documentation, saying, "Other parts of the system software, such as Mach, are based on technology previously used in Apple’s MkLinux project, in Mac OS X Server, and in technology acquired from NeXT." This particular message disappeared in 10.3 as there was no need to specifically call out the console as colour by then. default_pager), the virtual memory manager, is started next. The bootstrap loads the Linux kernel as the next task, which should start from its initrd and finally enter the installer. Unfortunately, at this point we got no more messages from the MkLinux Mach kernel and the 9150 completely ground to a halt. A brief moment of panic ensued because MkLinux used to work. Was the machine shot after all? After I got the conniption out of my system, I thought a little more carefully and realized that this new 9150 configuration was not the same as the old 9150 configuration: we have a ZuluSCSI, and we have approximately double the RAM. I could believe the ZuluSCSI was at fault if we were booting from it, but we were booting from the same CD-ROM I installed MkLinux from before, so that possibility seemed unlikely. That left pulling out half the RAM SIMMs. can do that much without having to disassemble the machine again to get the power supply out. It's putting the SIMMs back in that's the fun part, but right now we just have to make sure this can work. larger fifth free region. gcc 2.95.2 (I don't know where the line noise came from there, and I don't know why it says "hello"). The initrd then transfers control to the Red Hat installer, without using systemd, which we shall consider a feature. I ignored them, anyway. mac-us-std suffices. second volume on /dev/sdb. The two 2GB partitions we created are /dev/sdb3 and /dev/sdb4, which the MkLinux installer has treated as "Linux native." We move to /dev/sdb3 and select Edit. /dev/sdb3 will be /, so we provide that path and select OK. /dev/sdb4 will be /home. / and /home formatted, but we needn't check for bad blocks on them either, and select OK once again. / and /home are created, the packages are scanned ... glibc provided is 2.1.3. dreadfully slow, and the weak onboard video does it absolutely no favours. I recall finding AfterStep (ah, the irony), also included, more tolerable. Admittedly, Apple didn't really intend the 9150 to be a workstation and as such we're not going to struggle with it this time around, but we may possibly do so in a future article. brinton.floodgap.com. lilo.conf in the Mac side's Preferences folder to now point to the new root on /dev/sdb3. I have never tried this with an HFS file system, but it cannot mount (let alone write to) an HFS+ file system, so we'll need to do that on the 8.6 side afterwards. lilo.conf as instructed (from Preferences, or by clicking Custom...), and then restart the Mac. fsck since for some reason the installer did not cleanly unmount the root filesystem. Apple's Linux, after all, even though it was recently removed from the modern Linux kernel — and SMB services. If you had period clients, this could definitely have been your file server, and that would have been an absolutely appropriate purpose for the 9150. brinton:/home/spectre/% uname -a Linux brinton.floodgap.com 2.0.38-osfmach3 GENERIC_09 #9 Tue Mar 7 10:51:13 PST 2000 ppc unknown brinton:/home/spectre/% gcc -v Reading specs from /usr/lib/gcc-lib/ppc-yellowdog-linux/2.95.4/specs gcc version 2.95.4 20010319 (prerelease/franzo/20011204) brinton:/home/spectre/% ls -l /mach_servers total 3615 -rwxr-xr-x 1 root root 1349896 Jan 7 2001 Mach_Kernel -rw-r--r-- 1 root root 119033 Jan 7 2001 Mach_Kernel.map -rwxr-xr-x 1 root root 131314 Apr 19 2000 System.map -r--r--r-- 1 root root 123 Feb 22 1998 bootstrap.conf -rwxr-xr-x 1 root root 205690 Jan 7 2001 default_pager -r-xr-xr-x 1 root root 257371 Feb 22 1998 mach_init -rwxr-xr-x 1 root root 1614475 Apr 19 2000 vmlinux Sing it with me: Startup, shutdown, startup, shutdown, swiftly fly the rc files ...) Pimp My Green Giant Ride We have now returned to the functionality of our prior configuration: we can dual-boot MacOS and Linux. However, with motherboard video, the baseline CPU, half its maximum RAM, and half the L2 cache it originally shipped with, right now it's not a particularly strong performer in either operating system. Of course, the set of what upgrades are supported in Mac OS 8.6 is rather larger than the set of what upgrades are supported in MkLinux R2. For example, while G3 upgrades are available for the PDS connector in NuBus Power Macs and generally work well in Mac OS, most of them don't work in MkLinux, and MkLinux doesn't support NuBus video cards either. There is also the matter of that 128MB of RAM we had to remove that we would like back. Ideally, with these plastics continuing to crumble, we would like to find a maximal configuration suitable for both operating systems that can be changed through software as needed, rather than having to keep opening it up. MkLinux FAQ-O-Matic has somewhat conflicting information about which CPU upgrade cards work where and how, though the various reports do seem to agree that Sonnet cards won't work at all. On the other hand, while one reporter indicated success with a Newer Technology MAXpowr G3, I'm not entirely confident in his recollection because despite being a PDS card he said it displaced the L2 cache. The MAXpowr G3 PDS also lists the 6150 and 8150 but not the 9150 as compatible, even though the PDS connectors are the same; there may be a form factor limitation. But the 9150 is specifically listed as compatible for the Sonnet G3, and Sonnets are much more common. As long as the Sonnet extension loads after the MkLinux Booter extension, the upgrade card should remain inactive, and MkLinux can run unmolested on the 601 (we can leave the L2 cache stick in, even). For the Mac OS, we can use the G3 there. As it happens, I have a couple spare G3 cards in the stock closet and this seems like the perfect time to put one in service. the Power Mac car crash sound (further yelling censored). After unplugging it, pulling the G3 completely and putting the PDS terminator back in, I got the normal startup chime and a normal boot, so I put the G3 back in. This time it booted as well. (It also appears that the PDS terminator is not needed in this card; it seems to terminate the bus just fine by itself.) actual system bus is not doubled, which is to say that any bus access these CPUs make could stall depending on whether it corresponds to the real bus clock. These cards therefore must have a crapload of cache to smooth such gaps out, and indeed both of these fastest cards have a full 1MB of backside L2. The backside cache bus runs at double the system bus speed, but the cache is on the doubled "local" bus, so the cache speed is effectively 4x the system bus. The 9150/80 here is a 40MHz bus system, meaning the G3 card's local bus runs at 80MHz, the backside cache at 160MHz, and the CPU at a 5x multiplier for the full 400MHz. Even if I hunted down a 500MHz G3, and while I'm crazy I'm not made of money here, I would only see 480MHz on this system (6x multiplier). Sonnet's Metronome faithfully reports all these numbers, but Apple System Profiler doesn't know about the doubled "local" bus, so it erroneously thinks the CPU is only 200MHz. scri, then inserting spaces and/or changing its filename to sort above it. INIT to load it later. That way we can continue to boot MkLinux normally on the 601 (with the motherboard L2 cache, which we never removed), yet still enjoy the benefits of the G3 — and it is much snappier — in Mac OS 8.6. an -m option that caps the amount of RAM Mach will use. You pass it on the mach_options= line in lilo.conf, e.g., mach_options=-m136. To test this, we'll need to install the full 264MB. With some difficulty you can worm the SIMMs back in without removing the power supply. However, that also knocked the board back into the interrupt and reset switches again, and the second time around was not much more reassuring. Too much memory was apparently a significant problem in some configurations of MkLinux, and values that work for booting the installer for testing purposes may not necessarily work for an installation (for example, I tried -m200, and while this worked for the installer it could not start the kernel installed on the ZuluSCSI image). A cursory look at the Mach kernel source suggests that there is a hard upper limit on the number of VM hash buckets and when this overflows, the kernel goes into never-never land. -m191 seemed to be the highest value that would work either way, but I left it at -m136 since that was the RAM amount that we started with and that I knew was reliable. I was now pretty thoroughly irked by the problem with the front switches and decided I needed to do something about that. carefully at low speed. mostly stable. will use the Velcro straps to neaten everything up. I put the SCSI cable's excess length into the bottom compartment of the hard disk tray. much they wouldn't work. In my parts bin is a Radius PrecisionColor Pro 24XK. The reason I marked the name on it is that the 24X, 24XK and 24XP are closely related and can be hard to distinguish visually. From my experience the highest-end 24X has all 12 RAM chips along the top instead of the 11-1 arrangement here on the 24XK and 24XP. Another difference appears to be the speed of the Brooktree Bt473 RAMDAC, which is the silkscreened number at the end of the chip identifier (the 24X is 110MHz, this 24XK is 80MHz and the 24XP is 66MHz). Kan's page claims it does, so I just have to push it. This card has both composite video in and out as well as a conventional Mac DA-15 video port. Although the card is slower than the HPV card, it has 2MB of VRAM and correspondingly more colour depth and resolutions, so it's worth giving a shot. as directed and installed it that direction in the middle NuBus slot. It still needed some squeezing to make it and may have nailed the frame around the NuBus slots, but it fits. a future article and see if any hacks in the kernel are possible to improve the situation. If not, at least we'll have fun exploring the infrastructure. The Workgroup Servers did not survive the axe of Jobs either: after the 7250 and 8250, the last Macintosh systems to bear the Workgroup Server name were the Workgroup Server 7350 and Workgroup Server 9650 in April 1997, which were cancelled less than a year later in March 1998. With the introduction of the beige G3, the "Workgroup Server" brand was retired for the "Macintosh Server G3," which encompassed both the beige mini-tower in March 1998 and later the "Yosemite" Blue and White G3 in January 1999. Apple did not reintroduce bespoke server hardware to their product line until the rackmount Xserve G4 in 2002, which survived in G5 and Intel-based incarnations until 2011 when they were cancelled — per Jobs — "because hardly anyone was buying them"; a 2009 server version of the Intel Mac mini, in some ways a spiritual descendant of the Workgroup Servers, was itself cancelled in 2014. Even though there are still lots of Macs in offices, to date no official server hardware has ever returned to Apple's product line-up since.

2nd Aug 2026 1 votes
John C. Dvorak has died

Reports coming in of the death of John C. Dvorak, apparently passed away on Monday (July 20th) from complications of heart bypass surgery. Dvorak started in wine and moved to silicon as an early columnist at InfoWorld, one of the pillar references for this particular blog because of its studious regular reporting, and most notably for PC Magazine where he had no less than two columns since at least 1986. In addition to this and other tech journalism credits, he was on the flagship CNET Central cable TV program in the mid 1990's with his "Buy It, Try It, Skip It" reviews, plus other shows hosted on NPR and then-ZDTV. He was also a regular guest on Leo LaPorte's syndicated radio program and podcasts, and joined former MTV veejay Adam Curry (well known to us in Gopherspace for MTV Networks v. Curry, and an early user of TTYtter before Twitter started to suck) in 2007 as co-host of the No Agenda podcast, a role he maintained until his death. Dvorak hailed from a different generation of tech journalists, far more personal and yet far more technical, and even his detractors (given his political views, which we won't talk about here, there are many) would admit he was at least entertaining. He was 80. Rest in peace.

23rd Jul 2026 1 votes
Building an serial and VGA "everything console"

Some of our recent (and some upcoming) projects are oriented to systems with serial consoles, but it's been getting pretty old dragging around old CRT terminals or tying up Mac laptops with a serial port. I'd like something that's self-contained, a little more portable and a bit less heavy. I'm sure there's any number of all-in-one setups you can buy to do this, but I'm cheap, so I'm going to DIY it. from 2004 to 2014, so it's almost on-topic for this blog, even. I chose it because it was a little banged up and the LCD had some areas of damage (probably improperly closed on something), and the seller had priced it accordingly, but the screen was still sufficiently legible and the keyboard looked fine. Naturally you can do mostly what we're doing here with any of the similar Dell or HP or etc. units that can also be easily found. an UltraNav, the fact it gives you a choice of pointing device is rather nice: if you like TrackPoints (I don't hate them), you can use that, or if you prefer trackpads (I don't hate them either but I'd rather use the TrackPoint), you can use that. The keyboard and UltraNav are implemented as HIDs on a single hub which also offers two more USB ports. fine." Like I say, there was some damage, probably because it got closed improperly on something and messed up the display, but it's sufficient as a simple terminal or here connected to the M1 MacBook Air with a USB-C dongle. you need to do is pick what is the most convenient for you and has the right features. There are slightly more such devices which use a PS/2 port, but I decided to stick with USB since it would be more flexible and if I really needed something else, I could use an active converter like a ps2x2pico for PS/2 or a Wombat for Macs with ADB. I eventually selected this one from Tattler Solutions (not affiliated) because it ships from the United States (damn you UPS, you still owe me $600 on that tariff you stiffed me on), comes in a nice self-contained case and can be USB-powered, runs up to 115200bps, and has demonstrably good VT100 terminal support. All up it cost me $86 shipped. However, it also has a big drawback: its USB controller does not support combo devices like our IBM keyboard, which he does warn you about, and believe me, I tried really hard to get that to work because I really like the keyboard. Unfortunately, it truly is (and in fairness, as described) a fundamental hardware limitation that can't be programmed around, so that means we can't use our nice UltraNav. days. The first time I tried it, I tested it out after 24 hours as suggested and while the excess silane glue exposed directly to air had cured, the silane between the metal had only partially done so and the whole thing peeled right off. The second time I let it sit for a week. That seemed stable. Raptor Blackbird, using a VGA-to-HDMI dongle on the Blackbird side. Folded up more flat this time, ready for the next project. A latch and a handle would also help to make this more portable, though I suspect some drilling may be required for that. I'll look at some options. For now, this suffices.

14th Jun 2026 1 votes
Testing MacOS on the Apple Network Server 2.0 ROMs

It's time for another save point in the continuing saga of the various ROMs for the Apple Network Server, Apple's first through-and-through Unix server (previously, previously). The Apple Network Server was only ever officially able to boot AIX, IBM's proprietary Power ISA-specific Unix, though it was originally intended to run Novell NetWare and was demonstrated booting Mac OS with early pre-production ROMs. However, much to industry surprise, late in its life cycle then-CTO Ellen Hancock announced that the ANS would be able to boot Mac OS and even Windows NT as well using ROM upgrades. Neither ROM was officially released before Steve Jobs convinced Gil Amelio to cancel the line, and for many years they were believed to be vapourware. But they've started to surface, first with an ex-Apple employee who had both the preproduction ROM and the Mac OS ROM on a flash ROM SIMM, and later another employee turned up with the NT ROM, though sadly more is needed to make it actually run NT. It turned out that I also had the preproduction ROM in a box gathering dust, and a couple months ago we put both the preproduction ROMs and NT ROMs through their paces. holmstock, our hard-working Apple Network Server 700 test rig (stockholm, my original ANS 500, is still officially a production unit). And there are some interesting things to report, especially when we pit the preproduction ROMs and this set head-to-head in MacBench, and even try booting Rhapsody on it. When we last left the 700, it still had the preproduction 1.1.20.1 ROM installed, which can be used to boot either MacOS or AIX and makes it look to MacOS like a Power Macintosh 9500, the ANS's closest relative. However, the ANS has unique hardware: two Symbios Logic 53C825A SCSI-2 Fast and Wide controllers (20MB/s) for the internal SCSI bays and on-board Cirrus Logic 54M30 graphics used in no other Apple product. MacOS never supported these and the preproduction ROM does not contain support for them, so to boot Mac OS you have to use the external 5MB/s CURIO SCSI (the ANS doesn't have the typical Power Macintosh MESH controller) and a Mac-compatible PCI video card. I selected a IMS TwinTurbo, the same card shipped with the Power Macintosh 9500, and booted it off an external BlueSCSI, both of which work well for this purpose. Once you get everything set up, versions up to at least Mac OS 9.1 are compatible, though with various annoying glitches you have to work around because it's not really a 9500. very quickly to the patterned Toolbox background. We have nothing installed it can boot from, so it almost immediately displays a gimme-disk animation, just like a regular Mac. But unlike most other Old World Macs (and the preproduction ROMs), the icon is in colour, because this is Open Firmware 2.0. bye and boot words, there is an io word to redirect output for you. If you type ttya io at the console, it immediately switches to serial on rear port 2 at 38400bps by default. We want to see if it does anything interesting when we start it, so we'll setenv input-device ttya:57600, setenv output-device ttya:57600 and reset-all to default it to serial startup, but we still have to hold down Cmd-Opt-O-F or it snaps back into the Toolbox ROM. Let's dump the device tree. printenv VARIABLE CURRENT DEFAULT little-endian? false false real-mode? false false auto-boot? true true diag-switch? false false fcode-debug? false false oem-banner? false false oem-logo? false false use-nvramrc? false false real-base -1 -1 real-size 100000 100000 virt-base -1 -1 virt-size 100000 100000 load-base 4000 4000 pci-probe-list -1 -1 screen-#columns 64 64 screen-#rows 28 28 selftest-#megs 0 0 boot-device /AAPL,ROM /AAPL,ROM boot-file diag-device fd:\diags fd:\diags diag-file input-device ttya:57600 kbd output-device ttya:57600 screen oem-banner oem-logo nvramrc boot-command boot boot ok 0 > dev / ok 0 > ls Children of the node: FF82A4C8: / [AAPL,9500 MacRISC] Node Adr Node Name Compatible FF82B8B8: /cpus@0 FF82B9D0: /PowerPC,604@0 FF82BDE8: /l2-cache@0,0 FF82C528: /chosen@0 FF82C658: /memory@0 FF82C7A0: /openprom@0 FF82C860: /AAPL,ROM@FFC00000 FF82CA78: /options@0 FF82CF28: /aliases@0 FF82D168: /packages@0 FF82D1F0: /deblocker@0,0 FF82D9C8: /disk-label@0,0 FF82E4B8: /obp-tftp@0,0 FF830710: /mac-files@0,0 FF832508: /mac-parts@0,0 FF8336F0: /aix-boot@0,0 FF833B40: /fat-files@0,0 FF835158: /iso-9660-files@0,0 FF835AC0: /xcoff-loader@0,0 FF836388: /terminal-emulator@0,0 FF836420: /bandit@F2000000 FF837848: /gc@10 FF837C80: /53c94@10000 FF839490: /sd@0,0 [sd] FF83A1E0: /st@0,0 [st] FF83AE80: /mace@11000 FF83BD10: /escc@13000 FF83BE68: /ch-a@13020 FF83C4C0: /ch-b@13000 FF83CB18: /awacs@14000 FF83CC00: /swim3@15000 FF83E050: /via-cuda@16000 FF83EF00: /adb@0,0 FF83EFF0: /keyboard@0,0 FF83F910: /mouse@1,0 FF83F9C0: /pram@0,0 FF83FA70: /rtc@0,0 FF83FF10: /power-mgt@0,0 FF83FFD0: /lcd@1C000 FF840908: /nvram@1D000 FF8426D0: /pci106b,1@B FF8428A8: /54m30@F [pci1013,a0] FF8445F8: /apple53C8xx@11 [53c825] FF8471E0: /sd@0,0 FF8480D8: /apple53C8xx@12 [53c825] FF84ACC0: /sd@0,0 FF840AA0: /bandit@F4000000 FF84BD60: /pci106b,1@B FF841F30: /hammerhead@F8000000 ok 0 > devalias vci0 /chaos@F0000000 pci1 /bandit@F2000000 pci2 /bandit@F4000000 fd /bandit/gc/swim3 kbd /bandit/gc/via-cuda/adb/keyboard ttya /bandit/gc/escc/ch-a ttyb /bandit/gc/escc/ch-b enet /bandit/gc/mace scsi /bandit/gc/53c94 scsi-int /bandit/gc/mesh screen /bandit@F2000000/54m30@F scsi-int would point to the internal SCSI, and a path like /bandit/gc/mesh (GC in this case is Grand Central) would do so on a regular Power Mac, but the ANS doesn't have a MESH. That means trying to list the contents of a CD-ROM in the internal optical drive, ordinarily SCSI 0 on an ANS ... dir scsi-int/sd@0,0:,\ unable to open the DIR device ok 0 > dir /bandit/apple53C8xx@11/sd@0,0:,\ . 00000010 000023 000002048 000 000 .. 00000010 000023 000002048 000 000 DIAGS. 00000000 000024 000189345 000 000 NWSTART. 00000000 000117 003603460 000 000 OFWBOOT. 00000000 001877 000349998 000 000 TRANS.TBL 00000000 002048 000000655 000 000 ok 0 > boot /bandit/apple53C8xx@11/sd@0,0:aix loader: unrecognized client program format state not valid disk2:aix Device isn't there! can't OPEN: /bandit/53c825@11/sd@2,0:aixOpenFirmware1.1.20 To continue booting the MacOS type: BYE<return> To continue booting from the default boot device type: BOOT<return> ok 0 > devalias vci0 /chaos@F0000000 pci1 /bandit@F2000000 pci2 /bandit@F4000000 fd /bandit/gc/swim3 kbd /bandit/gc/via-cuda/adb/keyboard ttya /bandit/gc/escc/ch-a ttyb /bandit/gc/escc/ch-b enet /bandit/gc/mace scsi /bandit/gc/53c94 scsi-int /bandit/53c825@11 lcd /bandit/gc/lcd screen /bandit/54m30@F scsi-int2 /bandit/53c825@12 disk0 /bandit/53c825@11/sd@0,0 disk1 /bandit/53c825@11/sd@1,0 disk2 /bandit/53c825@11/sd@2,0 disk3 /bandit/53c825@11/sd@3,0 disk4 /bandit/53c825@12/sd@4,0 disk5 /bandit/53c825@12/sd@5,0 disk6 /bandit/53c825@12/sd@6,0 ok 0 > boot disk0:aix or boot /bandit/53c825@11/sd@0,0:aix will boot AIX from the internal CD-ROM. improves performance. It's not clear what this ROM does in that instance, and in any case there are bigger problems such as the absolutely preposterous processor speed (this is a 150MHz PowerPC 604e). The L2 cache, at least, is correctly detected as the standard 700 1MB. We'll come back to this too. knew it hadn't been that bad. It also reported a bogus amount of L2 cache (2MB) despite the figure in the NSDU. But both Gauge PRO and TattleTech said there was no cache. eighty-nine percent of the reference G3 — 48% faster! My suspicion is this can be chalked up largely to the complete fail on the L2 cache and possibly also to the RAM speed, but either way, it was shocking how badly the 2.0 ROMs performed. boot scsi/sd@2,0:0 scsi/sd@2,0:8,mach_kernel (using a "partition zero" loader to chain into the actual Rhapsody kernel). On the preproduction ROMs, we got a CLAIM failed from Open Firmware no matter what settings I tried. waitForInterrupt bombed out, possibly because of the different interrupt setup on the ANS compared to the 9500. is correctly detected. these 2.0 ROMs, or more elemental issues like the L2 cache support would have gotten fixed. It certainly does smooth out certain rough spots, it supports all the ANS hardware as advertised, and it was more reliable with reboots, but the speed penalty you'll take running it just doesn't seem worth it. I could only see myself running this version if I had to have the biggest, baddest, meanest AppleShare server with tons of SCSI drives in Mac OS. Is there a later version out there yet to be found that does deal with those problems? Meanwhile, I'm going to work on a patched version of Mac OS for this machine to fix the reboot problems in 1.1.20, which I believe should be solveable with some resource hacking. That said, I'm not done with these ROMs just yet: I think Rhapsody, at least, can be made to work but it clearly needs a kernel patch, and I suspect the former Apple employee who got it working on the 2.0 ROMs did just that. Performance might still be hideously bad, but it's nevertheless another solid Un*xy option for Apple's best beige Unix box, and it even has historical value. To be continued if I can get my hands on some Rhapsody source code. Anybody feel leaky?

3rd May 2026 1 votes

More in technology

Anecdotally, programmers dislike "reduce"

In short: from my experience, people like map and filter, but not reduce. I use functions like map and filter all the time. When I put that code up for review, my peers rarely complain. I get plenty of feedback about other decisions, but not about my use of map and filter. I cannot say the same for reduce. Often, when I’ve submitted a patch with reduce inside, I get a comment like, “this part is hard to read.” And I see reduce way less than map, filter, some, and so on. Anecdotally, I have come to believe that programmers don’t like reduce as much. I don’t know why, but I have a few theories: reduce is harder to read. reduce is less familiar. reduce can have worse performance compared to other options. reduce is less elegant in languages I use, like JavaScript, Python, and Swift. In my blissful stint as a Clojure developer, I did not get this feedback. I’m wrong, and I’m seeing a trend that’s not real. I usually just change reduce to something else and move on. Even though I prefer it, I don’t usually care much. But it’s a little social phenomenon I’ve observed, and I thought I’d document it. I’ve also noticed this less recently, possibly because code review is less thorough nowadays. Do you notice this? Do you like reduce? Please tell me.

3 days ago 1 votes
Microcode in Intel's 8087 floating-point chip: the scale instruction

In the 1970s, floating-point arithmetic was a mess. Computer manufacturers had a dozen incompatible arithmetic standards. Moreover, floating-point systems were designed around hardware simplicity rather than mathematical rigor, leading to problems with numerical stability. This changed when Intel introduced the 8087 floating-point coprocessor chip in 1980, designed to be as accurate as possible, even in the corner cases. The 8087 became popular because it could be installed in the IBM PC, making floating-point operations up to 100 times faster in applications ranging from spreadsheets to CAD. But more importantly, the 8087 became the floating-point standard used by most computers today. The 8087 implemented its instructions in complex low-level code called microcode. I'm part of a group, the Opcode Collective, that is reverse-engineering this microcode, and I've recently made some progress. In this post, I examine the microcode for one of the 8087's instructions—FSCALE—and describe how this microcode works. The FSCALE (Floating-point Scale) instruction provides a quick way to scale a number by a power of two, much faster than a multiplication. I figured that FSCALE was a simple, almost trivial instruction that would be straightforward to understand and explain. Spoiler: it is not simple. FSCALE uses over 140 micro-instructions and three levels of subroutine calls to handle many special cases. But the FSCALE microcode illustrates many interesting parts of the 8087, such as the shifter, the adder, and the exponent converter, and also reveals a hidden feature of the 8087, so hopefully you will find it interesting. To explore the microcode, I opened up an 8087 chip and created a high-resolution image with a microscope. The large microcode ROM is in the center, holding the 1648 micro-instructions that control the chip. The microcode engine on the left steps through the microcode, handling jumps and subroutine calls. The bottom half of the chip is the "datapath", the circuitry that performs floating-point calculations; it is split into a 16-bit datapath for the number's exponent and a 64-bit datapath for the number's significand (also known as the fractional part). Die of the Intel 8087 floating-point unit chip, with main functional blocks labeled. The die is 5mm×6mm. Click for a larger image. Zooming in on the bottom part of the chip shows the datapath circuitry; I've highlighted the relevant parts below.1 The exponent ROM holds various constants. The exponent converter is a specialized circuit that examines exponents, detects special values, and converts between exponent formats.2 The shifter is a large component; it allows a 64-bit3 value to be shifted left or right by arbitrary amounts. (I wrote about the 8087's shifter circuitry here.) The adder is the heart of the 8087's calculations; it is used in a loop for multiplication, division, and square roots. The B register holds one input to the adder, while multiple sources can provide the other input. The sum register holds the adder's output. The eight stack registers and the temporary registers hold floating-point numbers. A close-up of the 8087's datapath, showing functional blocks that are used by FSCALE. Details of the 8087 In this section, I'll explain some features of the 8087 that are important for the FSCALE microcode. To use the 8087, a programmer stores values in its eight internal registers, organized as a stack. Each register holds an 80-bit floating-point number. To optimize performance, each value in the register stack has an associated "tag" value, which is mostly invisible to the programmer.4 A tag labels a value as valid, special, zero, or empty. A "normal" floating-point value is tagged as valid. If the floating-point value is infinity, Not a Number (NaN), or a denormalized value, then it is tagged as special. A zero value is tagged as zero. Finally, if a register is empty (e.g., its value has been popped off the stack), the register is tagged as empty. The 8087 also has temporary registers that it uses internally: tmpA, tmpB, and tmpC. Like the stack registers, tmpA and tmpB are 80-bit registers, along with two tag bits. However, tmpC only holds a 64-bit significand. The 8087 supports a variety of data types: floating-point numbers of various sizes, integers, and binary-coded decimal. But internally, everything is stored as an 80-bit floating-point number called a "temporary real"; for the rest of this article, I'll only be considering temporary real values. A number has three parts: the sign bit, the 15-bit exponent, and the 64-bit significand (the fractional part), In most cases, a floating-point number is represented by sign × significand × 2exponent. The significand is a 64-bit binary number of the form 1.bbb...: a leading 1, followed by the binary point (the binary equivalent of the decimal point) and the rest of the bits.5 What makes floating-point numbers useful is that their scope covers the incredibly small to the astronomically large, thanks to the exponent, which ranges from -16382 to 16383. One important detail is that the exponent is stored with a "bias" of 16383 added to it. Thus, the stored exponent is always positive, even if the real exponent is negative.6 The 80-bit temporary real format. The triangle indicates the binary point, analogous to the decimal point. From the Intel Numerics Supplement. The 8087 supports several types of numbers that are represented as special cases with special exponents, as shown below. Zero and infinity have both positive and negative values. "Not a Number" (NaN) represents values that don't make sense, such as 0/0 or sqrt(-1); NaN has a large number of representations, not a single value. The 8087 also supports denormalized and unnormalized values, which are extremely small values where the significand doesn't have a leading 1. The encoding of special values. Based on Table S-31 in the Intel Numerics Supplement, but highly simplified. The "x" bits are arbitrary, as long as they don't conflict with another type. The 8087 has a complicated exception system with six types of exceptions to indicate if something went wrong with an arithmetic operation. The most serious is the "invalid operation", indicating that the operation does not make sense, such as 0/0 or ∞-∞. It also includes accesses to an empty register (stack overflow or underflow) or operations on a NaN value. The 8087 also has an overflow exception if a value is too large to store, an underflow exception if a value is too small, and a divide-by-zero exception (excluding 0/0). A denormalized operand exception indicates that the result is too small to store as a normal value, but can be stored as a denormalized value. Finally, a precision exception indicates that a value cannot be represented exactly and must be rounded. (Precision exceptions are very common; even 1/10 will yield one.) The 8087 provides fine-grain control over each exception type, specified by bits in the control register. If an exception is unmasked, the 8087 sends an interrupt to the 8086 processor, which handles the problem in software, for instance by terminating the program or logging an error. Alternatively, the exception can be masked and the 8087 will continue execution as best it can. For instance, an invalid result will be replaced by NaN, while an overflow or divide-by-zero will be replaced by infinity. A precision exception will result in rounding. The point of masked exceptions is that calculations continue, yielding an answer that is as accurate as possible; in most cases, this is what the programmer wants. These features make the 8087 flexible and provide accuracy, but they also make the microcode much more complicated, since the combinations of special cases need to be handled appropriately. The 8087's microcode Executing an 8087 instruction can require hundreds of internal steps to compute the result. These steps are implemented in microcode with micro-instructions that specify each step of the algorithm. (Keep in mind the two levels of instructions: the assembly language instructions used by a programmer and the undocumented low-level micro-instructions inside the chip.) The microcode ROM holds the 1648 micro-instructions that implement the 8087's instruction set. I'm working with the Opcode Collective to reverse-engineer the micro-instructions and fully understand the microcode (link). The 8087's micro-instructions are complicated, with many corner cases and ad hoc functions, but I'll provide a simplified overview. Each micro-instruction consists of 16 bits, as shown below. The first three bits specify the micro-instruction's type, which controls the meaning of the remaining bits. The first type is a transfer operation, which transfers data from one internal register to another. The two fields specify the source and destination. The three remaining bits are used for various special cases. Next is a shift operation, which uses the barrel shifter to shift a value left or right. The third type of micro-instruction controls the adder (which can also subtract). The miscellaneous instructions include stack pointer operations, tag modification, exceptions, and subroutine return. The far jump and far call micro-instructions perform a jump or subroutine call to a target micro-address in a fixed list. The condition field allows conditional jumps/calls/returns based on numerous conditions, while the last bit inverts the condition. A local jump is a relative jump to a nearby micro-instruction. Structure of an 8087 micro-instruction. The FSCALE microcode When the 8087 starts executing an instruction, the instruction decoder circuitry determines the starting address of the microcode corresponding to the instruction. This 11-bit address is loaded into the microcode engine, which starts executing the microcode.7 The microcode for FSCALE (shown below) starts at decimal address 748.8 The idea behind FSCALE is straightforward: if you want to scale a floating-point number by 2N (for an integer N), you add N to the number's exponent. This allows you to multiply or divide by a power of two much faster than using the full floating-point multiplication operation. However, the microcode for FSCALE is unexpectedly complicated and uses several microcode subroutines. In brief, the microcode first checks for arguments that are zero and then handles other special arguments. It converts the scale argument to an integer and adds it to the exponent. Finally, it handles any overflow or underflow. In more detail, the microcode routine starts by moving the first argument from the top of the stack (st(0)) to the tmpA temporary register. If the argument is zero, the routine immediately returns. (Thus, scaling 0 by anything—even NaN—will give a result of 0.) Next, the second value on the stack (the second argument) is moved to the tmpB temporary register. Likewise, the code returns if this value is 0, so scaling anything by 0 leaves the value unchanged.9 Next, a constant value is selected; selecting a constant and using it are two separate micro-instructions. (The 8087 has separate ROMs for 16-bit exponent constants and 67-bit significand constants; this one is an exponent constant.) In the normal case, execution jumps to address #0763, skipping the call to subroutine SPECIAL_TMPS. FSCALE: #0748 st(0) -> tmpA Input argument from top of stack #0749 jmp #0776 if tmpA:tag ZERO Bail if 0 #0750 stackPtr++ #0751 st(0) -> tmpB Scale argument from stack(1) #0752 stackPtr-- #0753 jmp #0776 if tmpB:tag ZERO Bail if 0 #0754 expconst 0x403e Const 403e: exp shift to convert to int #0755 jmp #0763 if not tmp empty/special/div #0756 call SPECIAL_TMPS Special handling #0757 jmp #0762 if flag #0758 jmp #0761 if not tmpB:tag SPECIAL #0759 except:invalid Invalid exception, use NaN #0760 NaN -> tmpA #0761 jmp #0776 if intr #0762 jmp #0775 if expConv[0] Return tmpA if expConv set, otherwise continue #0763 tmpB:exp -> Breg Normal path #0764 tmpB:sign,exp -> expConv ExpConv will test tmpB's sign #0765 expConst -> tmpC Const 403e #0766 adder: tmpC - Breg cin=1 403e-exp is amount to shift to convert tmpB to int #0767 sumreg:frac -> shiftcount Store in shifter control #0768 shift tmpB:frac R count byte bit Perform the shift #0769 shift R -> Breg Breg holds scale argument as an int #0770 jmp #0777 if neg Negative Breg needs separate handling #0771 adder: tmpA:exp + Breg cin=0 Add the scale to the exponent #0772 sumreg:frac -> expConv Put result in expConv to check #0773 sumreg:frac -> tmpA:exp Update exponent with sum #0774 call NONNORMAL_RESULT if not exp normal Handle overflow/underflow #0775 tmpA -> st(0) Save result back to stack #0776 RNI Done: Run Next Instruction #0777 adder: tmpA:exp - Breg cin=1 Subtract Breg #0778 jmp #0772 Continue processing Continuing at #0763, the second argument is converted from a float to an integer, which takes a few steps. For example, suppose the argument is 9, which in floating point is 1.001×23. The significand bits 1000 are "left justified", but for an integer, these bits need to be "right justified" by shifting them to the right. In general, if the exponent is n, the significand is shifted right by 63-n bits. But recall that the exponent is biased by 16383. Thus, the significand must be shifted right by 63-(exp-16383) bits, that is 0x403e-exp bits. (This explains the constant 0x403e earlier in the microcode.) Converting a float to an int by shifting. In the microcode, the subtraction takes several steps. At #0763, the exponent of the second argument is moved to the B register, one of the inputs to the adder (completely different from tmpB).10 Next, the sign and exponent are moved to the exponent converter, a circuit that, among other things, tests for overflow. Next, the constant 0x403e (selected back at #0754) is moved to the tmpC register. At #0766, the adder is activated, subtracting the exponent from the constant.11 The adder puts the result into the sum register, and this value is copied to the shift count register, which controls the shifter. This value indicates how many bits the second argument must be shifted to convert it to an integer. At #0768, the shifter is activated to shift by the desired amount, using both the bit shift part and the byte shift part. As with the adder, activating the shifter and reading the result are separate micro-instructions; the result is put into the B register. The core part of the FSCALE instruction is finally performed at #0771, adding the second argument to the first argument's exponent. The adder is activated to add the B register value (the scale) to the exponent, and the updated value is stored in tmpA's exponent. (Except if the scale factor is negative, it is subtracted via the #0777 path.)12 The value is also sent to the exponent converter circuit, which checks the exponent for overflow or underflow; if so, subroutine NONNORMAL_RESULT is called. But in the normal case, the updated value is copied from tmpA to the top-of-stack register st(0). Finally, RNI (Run Next Instruction) indicates that the microcode routine is done and the instruction is completed. Thus, even in the straightforward case, FSCALE takes about 22 micro-instructions. Handling empty or special arguments What happens if an argument accesses an empty stack location (i.e. stack underflow) or is a special value (infinity, denorm, NaN)? These cases are handled by a micro-subroutine that I'll call SPECIAL_TMPS15 because it processes special values in tmpA and/or tmpB. This subroutine is a general-purpose routine, used by basic arithmetic operations, FSCALE, FTST (test), and FPREM (partial remainder). The control flow through SPECIAL_TMPS is rather convoluted since the code must prioritize issues if, say, one argument is empty and the other is a denorm. I'll just give a brief summary; see the footnote13 for details. First, the subroutine converts any denorms to unnorms. Then it checks for access to empty stack locations, raising an exception or interrupt if so. Then it checks the two arguments again. If either is NaN, an exception or interrupt is triggered. Otherwise, it returns a status indicating the type of arguments. Unexpectedly, if both arguments are NaN, the code compares the two NaN values and returns the larger. This behavior may seem very weird, but it's a documented feature.14 You might think that NaN is a single value, but it's actually an enormous family of values. The idea was that the programmer could use different NaN values to signal where a problem occurs. For instance, you could put a different NaN in each location of an uninitialized array, so you could tell which position was accessed. For some reason, the designers of the 8087 decided that if you perform an operation with two different NaNs, the result is the larger one. Thus, the microcode needs code that detects if both operands are NaN and computes the larger, using a subtraction for the comparison (#1518). SPECIAL_TMPS (J5): #1484 call SPECIAL_VAL if tmpA:tag SPECIAL Handle special values in tmpA/tmpB #1485 xchg tmp #1486 call SPECIAL_VAL if tmpA:tag SPECIAL Handle tmpB special #1487 xchg tmp #1488 1 -> flag Flag=1 by default #1489 jmp #1500 if not tmp empty/special/div 0 -> expConv if tmps okay #1490 1 -> expConv #1491 jmp #1497 if not tmpA/B empty #1492 except:invalid Invalid if either empty #1493 jmp #1525 if compare instruction No NaN for comparison #1494 jmp #1511 if intr Return if interrupt not masked #1495 NaN -> tmpA NaN if interrupt masked #1496 return #1497 jmp #1502 if tmpA:tag SPECIAL Special cases #1498 jmp #1505 if tmpB:tag SPECIAL #1499 0 -> flag Div normal path: #1500 zero -> expConv Return flag 0, expConv 0 #1501 return #1502 call SPECIAL_VAL TmpA special #1503 jmp #1512 if not flag Jump if NaN, fallthrough if infinity #1504 jmp #1509 if not tmpB:tag SPECIAL #1505 xchg tmp TmpB special #1506 call SPECIAL_VAL #1507 xchg tmp #1508 jmp #1521 if not flag Jump if NaN, return if infinity #1509 0 -> flag Clear flag, return #1510 return #1511 RNI End instruction with interrupt #1512 jmp #1522 if not tmpB:tag SPECIAL TmpA NaN, now check tmpB #1513 xchg tmp #1514 call SPECIAL_VAL Check tmpB #1515 xchg tmp #1516 jmp #1522 if flag Jump if tmpB is not NaN #1517 except:invalid Invalid exception #1518 tmpB:frac -> Breg Both args are NaN, find larger #1519 adder: tmpA:frac - Breg cin=1 #1520 jmp #1522 if adder sign See if tmpA #1521 tmpB -> tmpA Take larger #1522 except:invalid Invalid exception #1523 jmp #1525 if compare instruction No interrupt for comparison instruction #1524 jmp #1511 if intr End instruction with interrupt #1525 1 -> flag Return with flag set #1526 return End of J5 This subroutine makes heavy use of a helper subroutine, SPECIAL_VAL,16 that processes one argument. The helper converts a denormalized argument to an unnormalized argument, raising an exception or interrupt as appropriate. It also flags an input of infinity. The hardware for the micro-instruction that exchanges tmpA and tmpB at #1485 is interesting. Instead of physically moving the values between the two registers, the micro-instruction toggles a flip-flop that exchanges the meaning of tmpA and tmpB. That is, if the flip-flop is set, a reference to tmpA goes to tmpB and vice versa. (This is a standard trick in microprocessors; the Intel 8080's XCHG instruction exchanges the DE and HL registers in a similar way. The Z80 uses the same trick for the EX and EXX instructions to exchange the regular register set with the secondary register set.) The Intel 8087 chip is packaged in a 40-pin DIP (dual in-line package), as are the 8080 and Z80. This photo is here as a break from all the microcode. Handling a non-normal result If you take a very large number and scale it larger, you can end up with overflow. If you take a very small number and scale it smaller, you can end up with a denormalized number or underflow. This will trigger an overflow, denorm, or underflow excaption, and an interrupt if unmasked. Moreover, the 8087 supports four rounding modes: round to nearest valid value, round down (toward -∞), round up (toward +∞), or round (chop) toward zero. Depending on the rounding mode, an overflow can result in either ∞ or the largest possible floating-point number. Similarly, an underflow can result in either zero or the smallest possible floating-point number. And depending on the infinity mode (affine or projective), infinity can be either signed or unsigned. Thus, the FSCALE microcode needs to handle many special cases for the result. The subroutine to handle a non-normal result in tmpA is below. One interesting micro-instruction is update overflow/underflow exceptions, which triggers an exception if appropriate. For most exceptions, a micro-instruction triggers the exception (for example, except:precision at #0346). But for the overflow and underflow exceptions, the microcode delegates the task to hardware. Specifically, the 8087's "exponent converter" circuit examines the exponent to see if an overflow or underflow exists, based on the selected floating-point precision. The micro-instruction sets the overflow and underflow flags based on these values. Thus, a complex task is performed by a single microcode instruction, thanks to the hardware support of the exponent converter. NONNORMAL_RESULT (J16): #0318 return if tmpA:tag ZERO Handle non-normal result #0319 update overflow/underflow exceptions Trigger exceptions if exp conv says to #0320 expconst 0x6000 The interrupt bias constant 0x6000 #0321 jmp #0329 if not intr #0322 expConst -> Breg Interrupt path #0323 jmp #0326 if neg #0324 adder: tmpA:exp + Breg cin=0 Add bias for underflow #0325 jmp #0327 #0326 adder: tmpA:exp - Breg cin=1 Subtract for bias overflow #0327 sumreg:frac -> tmpA:exp New exponent to tmpA #0328 return Interrupt, so done #0329 jmp #0344 if neg Masked exception #0330 tmpA:exp -> Breg Underflow #0331 adder: 1 - Breg cin=1 Amount to shift denormal #0332 call CREATE_DENORM Create a denormal #0333 adder: zero + Breg cin=0, roundmode Add zero to round #0334 call ADJUST_PRECISION Adjust to specified precision #0335 jmp #0340 if Sum register is zero If zero, return +/- zero as appropriate #0336 zero -> tmpA:exp Denorm: exponent is 0 #0337 sumreg:frac -> tmpA:frac Save denorm fraction #0338 special -> tmpA tag Tag denom as special #0339 return #0340 tmpA sign -> sign latch Return +/- zero #0341 zero -> tmpA #0342 sign latch -> tmpA sign #0343 return #0344 NaN/Inf -> tmpA:exp Overflow: maybe return infinity #0345 tmpA:frac -> tmpB:frac Save tmpA frac in tmpB #0346 except:precision Set precision exception #0347 Inf -> tmpA:frac Put infinity in frac #0348 special -> tmpA tag Mark infinity as special #0349 return if not round chop If rounding up, return infinity #0350 1 -> Breg Return max float: adjust down #0351 adder: tmpA:exp - Breg cin=1 #0352 sumreg:frac -> tmpA:exp Exp=7fff-1=7ffe #0353 adder: zero - Breg cin=1 #0354 sumreg:frac -> tmpA:frac Frac 0-1 = ff...ff #0355 norm -> tmpA tag Normal value #0356 return if tmpB:frac[63] Return max float unless unnorm #0357 tmpB:frac -> tmpA:frac Return original tmpA frac #0358 return The 8087 has interesting behavior if an overflow or underflow is unmasked and an interrupt occurs. The idea is to let the interrupt handler know what the exponent should have been. However, the proper value can't be used since it is too big or too small to fit in the exponent field (which is why the exception occurred). The solution is to add or subtract the constant 0x6000, resulting in an exponent that fits. The interrupt handler can subtract or add this constant to get the correct exponent. Lines #0322 to 0328 perform this addition or subtraction. For a masked underflow, a denorm value is created by the subroutine CREATE_DENORM. The value is rounded to the specified precision by ADJUST_PRECISION. Finally, if the value is too small for a denorm, the value +0 or -0 is returned as appropriate. For a masked overflow, the 8087 either returns Infinity or the largest-possible float, depending on the specified rounding mode. Infinity is represented by an exponent of all 1s, and a significand of 1000...; these values are loaded directly onto the bus by transistors. The maximum float, however, is computed: 1 is subtracted from the infinity exponent, and 1 is subtracted from a zero significand. Helper subroutine: creating a denormal One controversial feature of the 8087 is denormals, numbers that are smaller than "regular" floats. Recall that floating-point numbers have a significand with the first bit set to 1. But what happens if you hit the smallest possible exponent and want an even smaller number? The 8087 lets you break the rule that the significand starts with 1, producing smaller numbers known as denormalized numbers or denorms. Denorms significantly extend the range, providing numbers up to a factor of 263 smaller. However, denorms don't have as much precision since the upper bits are "wasted". Moreover, calculations with denorms can be substantially slower because special handling is required. Example of a normal number, reduced by a factor of 8, resulting in a denormal. The diagram above shows a normal number with the minimum possible exponent (-16382, which is 1 after biasing). Dividing the number by 8 (or scaling by -3) creates a denorm since the exponent can't be reduced any further. Instead, the significand is shifted 3 bits to the right. The exponent is replaced with the special value 0, indicating that the number is a denorm. In the 8087, denorms are created by a microcode subroutine that I'll call CREATE_DENORM; it is used by many arithmetic operations, not just FSCALE. This subroutine takes a normal number and a shift amount. By shifting the normal number (as in the example above), it creates a denormalized number. The microcode (below) uses the exponent converter to check if the shift is 64 or more. If so, there will be nothing left after the shift, so zero is returned. Otherwise, the value is shifted to the right and the denorm is stored in the B register. CREATE_DENORM (J20): #0522 sumreg:frac -> expConv Create denorm #0523 sumreg:frac -> shiftcount Number of bits to shift #0524 jmp #0528 if exponent[6:14] == 0 Jump if #0525 zero -> Breg No bits left, use zero #0526 shift tmpA:frac L 0 bytes, 0 bits Run through shifter? #0527 jmp #0532 #0528 shift tmpA:frac R count byte bit Shift right by the specified amount #0529 shift R -> Breg Result to Breg #0530 shift tmpA:frac L ~count byte bit Now shift back for sticky test #0531 NOP Wait for shifter #0532 rounding(h) -> Breg[grs] Store the three rounding bits in the Breg #0533 return But why is the value then shifted to the left (#0530)? The purpose of this is to get the rounding bits. One of the principles of the 8087 is to get rounding correct, which is a lot harder than it seems. In order to decide how to round up a number, you need to keep track of an impossibly large number of bits. For instance, if you calculate 1 + 0 and round up, you get 1. But if you calculate, say, 1 + 2-10000 and round up, you get a float a bit higher than 1. The problem is how do you distinguish the two sums before rounding, without storing thousands of bits? The trick is that the 8087 keeps three bits for use in rounding: the "guard" bit, the "round" bit, and the "sticky" bit. If you consider a "tail" of bits to the right of the significand, the guard bit is the most significant bit of the tail, followed by the round bit. The sticky bit is special: it is the OR of all the remaining bits in the tail, indicating if any of them are 1. Thus, 1 + 2-10000 has the sticky bit set, while 1 + 0 does not, so the two values can be rounded up differently. To generate the sticky bit, the 8087 uses a very large 64-bit NOR gate that tests the tail bits in parallel. A diagram showing how the guard, round, and sticky bits are computed from a right shift. The numbers in this example are different from the previous example. When a number is shifted to the right (e.g., when creating a denormal), bits are lost off the right. To generate the rounding bits, the value is shifted to the left, keeping all the tail bits that will eventually be discarded, and discarding the bits that will be in the final significand. The top two bits go into the guard and round bits, while the remaining bits are ORed together to generate the sticky bit from the rest.17 The diagram above is an example of this process. Suppose the value is being shifted to the right by 4 bits. The tail bits abcd (or at least d) will get lost in the shift. The rounding bits are computed by shifting the original significand to the right by 59 bits (the complement of 4). Bit 62 (a) becomes the new guard bit, bit 61 (b) becomes the new round bit, and the OR of the remaining 64 bits becomes the new sticky bit. (Note that the old guard, round, and sticky bits get ORed in too, so they aren't lost.) Merging the significand from the first shift with the rounding bits from the second shift produces the desired result. Helper subroutine: adjusting precision Although the 8087 supports three lengths of floats, it performs all calculations with 80-bit "temporary reals". At the end of an instruction, it converts the result to the desired length. (As a consequence, most instructions aren't any faster if you use a shorter float.) A microcode subroutine, which I call ADJUST_PRECISION, converts the result to the precision that is specified in the 8087's control word, using the specified rounding mode. This subroutine is used by most of the arithmetic instructions. The 8087 supports three types of real numbers. From the Intel Numerics Supplement. The first code path handles temporary reals (which have 64 bits of precision). The control word specifies one of four rounding modes. However, there are only two actions that can be taken for a particular significand: either round down (chop) or round up (chop and increment by 1). This decision is made by complicated logic circuits that examine the rounding bits, the rounding mode, and the sign to determine whether to round up or down. This simplifies the microcode but makes the hardware more complicated. The microcode performs a conditional return, returning if the significand doesn't need to be rounded up. Otherwise, the microcode increments the significand by adding 0 with a carry-in. It then checks for overflow, in which case it replaces the value with Infinity and sets a special flag.18 ADJUST_PRECISION (J11): #0299 jmp #0306 if not precision64 #0300 return if not round up, update CC1 Update condition code, maybe return #0301 adder: sumreg:frac + 0 cin=1 Add 1 to round up #0302 return if not sumreg[64] #0303 Inf -> sumreg:frac,sign Return infinity if overflow #0304 2count++ Set special flag #0305 return #0306 23/52 -> shiftcount Short or long real: get appropriate shift #0307 shift sumreg:frac,rnd L count byte bit sticky Shift to generate rounding bits #0308 NOP Wait for shifter to complete #0309 rounding(H) -> sumreg[grs] Store rounding bits #0310 shift sumreg:frac R ~count byte bit Shift right to drop excess bits #0311 shift R -> sumreg:frac #0312 jmp #0314 if not round up, update CC1 Update condition code #0313 adder: sumreg:frac + 0 cin=1 Round up if appropriate #0314 shift sumreg:frac L ~count byte bit Shift left to realign #0315 shift L -> sumreg:frac,sign #0316 return if not sumreg[64] Return if not overflow #0317 jmp #0303 Return infinity The code is more complicated when returning a smaller precision (short real or long real), since the significand must be shortened. First, the code at #0306 loads the shifter with either 23 or 52, depending on the precision specified in the control word, and then shifts the value left. This produces the rounding bits as in the previous section. Next, the value is shifted to the right, shortening it to the desired length. As before, the significand is incremented or not, depending on whether it should be rounded up or not. Finally, the value is shifted back to the left, so the most significant bit of the significand is on the left. As before, if rounding up caused an overflow, infinity is returned. One bizarre feature is that a jump with the "round up" conditional also has a side effect of updating the 8087's programmer-visible condition code register (CC1), indicating if the result was rounded up or down. That is, the 8087 has extra circuitry to detect this specific condition and load the value into the condition code latch. Strangely, the 8087 documentation doesn't describe this condition code action; Intel didn't document it until the 387SX floating-point chip in 1987.19 Conclusions Floating-point has a long history before the 8087. For instance, the IBM System/360 mainframes (1964) supported 32-bit and 64-bit floating-point numbers. In 1977, AMD introduced the Am9511 floating-point chip, supporting 16- and 32-bit floating-point numbers, along with transcendental functions. What made the 8087 revolutionary is that it was carefully designed to be as mathematically accurate as possible, largely thanks to numerical expert William Kahan. (The 8087 led to the IEEE 754 Standard, now used by almost every computer and ending the anarchy of incompatible floating-point standards.) The 8087 ended up extraordinarily complicated with three different sizes of floating-point numbers, four sizes of integers, four rounding modes, infinity modes, a collection of exceptions that could be masked or unmasked, denormalized and unnormalized numbers, signed and unsigned infinities, signed zeros, and a whole family of Not-a-Numbers. These features combine, yielding many corner cases. The 8087 deals with this complexity both through specialized circuits and through tangled microcode. How complicated is the 8087? For users who didn't have an 8087 chip, Intel sold an 8087 Support Library that exactly emulated the 8087's instructions (but much slower). The emulator took 16K bytes of 8086 code, which was a lot when a full BASIC interpreter could fit in 8K. Another way of looking at this is that the hardware of the 8087 drastically reduced the amount of software required: the 8087 itself used 3.3K of microcode, compared to the 16K for the emulator in 8086 code. I plan to continue reverse-engineering the 8087 microcode; for updates, follow me on Bluesky (@righto.com), Mastodon (@[email protected]), or RSS. I've been working on this with the members of the "Opcode Collective", especially Smartest Blob and Gloriouscow, who converted the ROM images to microcode data and extensively analyzed the contents. See the 8087 repository on GitHub for more. Notes and references The 8087 patents provide some details on the hardware, but unfortunately not the microcode. The patent diagram below shows the architecture of the 8087; I've highlighted the relevant parts. The fraction bus and exponent bus are shown in red. The adder and associated registers are in yellow. (For subtraction, the B register selector selects the complement.) The shifter is in green. The exponent constant ROM and the exponent converter are in orange. The temporary registers and stack registers are in blue. The architecture of the 8087. Based on the patent. Click this image (or any other) to magnify.  ↩ The exponent converter is surprisingly complicated because the 8087 has three different formats for floating-point numbers with three different sizes of exponent fields (8 bits, 11 bits, and 15 bits). Moreover, the different sizes of exponents are stored with different biases. Thus, converting between different sizes of exponents is not trivial. The exponent converter also recognizes overflow and underflow for the different exponent sizes, as well as special values such as infinity and NaN. I plan to describe the exponent converter in more detail later. ↩ The significand in the 8087 is nominally 64 bits wide. However, the 8087 uses three extra low-order bits for rounding, called Guard, Round, and Sticky. These bits ensure that a value is always rounded in the right direction. Some parts of the datapath have additional bits for sign or overflow: the shifter is 68 bits wide, and the adder is 69 bits wide. For the most part, I'll ignore these extra bits and refer to the datapath as 64 bits wide. ↩ Tags are normally invisible to the programmer, but can be accessed through special operations. Specifically, a programmer can dump the 8087's state to memory; the tags are stored in a 16-bit "tag word". ↩ The external representations of floating-point numbers have an implied leading one, with only the bits after the binary point explicitly stored. This provides one additional bit of resolution "for free". The internal 80-bit representation, however, has an explicit leading one to simplify calculations. ↩ One reason that the exponents are biased is that to find the larger of two floating-point numbers, you can compare them lexicographically as signed integers, rather than needing to examine the exponents separately. ↩ Most of the 8087's instructions are implemented in microcode, but a few are hard-wired. For more details on instruction decoding, see Instruction decoding in the Intel 8087 floating-point chip. ↩ I use decimal addresses for the microcode because the Opcode Collective started using decimal addresses, and it would be confusing to change now. ↩ The microcode shows that scaling 0 by anything, or scaling anything by 0, leaves the value unchanged. My view is that the designers took a shortcut here, rather than returning the "right" value. Since the 8087 defines 0×∞ as NaN, it seems to me that 0×2∞ should also be NaN, so FSCALE(0, ∞) should be NaN, not 0. The designers probably made the valid decision that nobody really cared about corner cases on the obscure FSCALE instruction. For other instructions, the behavior with denormals, unnormals, and zeros is documented (tables S-24 to S-26 in the Numerics Supplement documentation), but FSCALE is omitted. ↩ The 8087 has separate buses for the exponent and the significand, and the adder is only connected to the significand bus, so how does the exponent get to the adder? The trick is that there is a 16-bit gateway between the exponent bus and the significand bus, so the exponent can be copied over. ↩ I described the 8087's adder here. In brief, subtraction is performed by inverting the B register's value when it is fed into the adder. The carry-in to the adder is set to 1, so this in effect performs a two's-complement subtraction. ↩ Why does the microcode have separate paths to add a positive scale and subtract a negative scale? The reason is that values are stored as a sign bit and an unsigned value, not two's complement like standard integers. As a result, the adder can't perform signed addition directly. Instead, the adder circuitry must be explicitly directed to complement the B register value and perform a subtraction. ↩ This flowchart shows the SPECIAL_TMPS subroutine. The structure of this routine is complicated because paths split off and rejoin. One tricky path is the code to determine if there are 0, 1, or 2 NaN values, and take the maximum NaN if there are two. Another complication is the exception exits, which raise an interrupt if the interrupt is not masked, but not for a comparison instruction. The two return values are returned through flag and expConv. A flowchart for the SPECIAL_TMPS subroutine. Click for a larger version. The actions of SPECIAL_TMPS are summarized below. It returns status through the flag flip-flop and the exponent converter register (expConv). Its actions are: table.status {border-collapse: collapse;} table.status tr:first-child {border-bottom: 1px solid #ccc;} table.status th,td {padding: 0 10px; text-align: center;} table.status th:first-child,td:first-child {border-right: 1px solid #ccc;} InputResultflagexpConv emptyNaN, exception11 NaN(larger) NaN, exception11 infinityinfinity01 denormunnorm10 div abnormalno change00 (The last row signals an abnormal value during division computation; I'm still investigating this.) ↩ Prof. William Kahan, who guided the development of the 8087, was disappointed that some floating-point features were unused because of a vicious circle: the features didn't receive good compiler support, so programmers didn't use the features, so compiler developers claimed a lack of demand for the features and didn't implement support. Using multiple values of NaN to record how and/or where an NaN came into existence was an example of a feature that lacked software support. See Lecture Notes on the Status of IEEE Standard 754 for Binary Floating-Point Arithmetic for a detailed discussion of NaN and other issues. ↩ The 8087 makes heavy use of micro-subroutines, with a 6-level stack for microcode subroutine calls. Microcode jumps and subroutine calls get the address from a jump table. The index from the jump table comes from 6 bits of the micro-instruction. We unimaginatively named the entries in the microcode jump table as J0, J1, and so forth based on the index, but I'm adding more meaningful names as I figure them out. As for the names for micro-instructions, we don't have any information on what names were used by Intel (unlike the 8086). I invented names, influenced by the names in Gloriouscow's disassembly. ↩ A subroutine that I call SPECIAL_VAL handles denormalized values, infinity, and NaN. (This subroutine is primarily used by SPECIAL_TMPS, but is also used by FRNDINT (round to integer) and FSQRT.) First, the subroutine looks at the exponent of tmpA; if the exponent is zero, the value is denormalized. (The value could also be zero, but that was handled earlier.) If so, the denorm exception is set. Comparison instructions such as FCOM handle denorms differently, but I'll ignore that for now. The code at #1576 tests if the denorm triggered an interrupt; if so, the instruction ends with the interrupt. If the interrupt was masked, the code converts the denorm to an unnorm by changing the tag to norm and changing the exponent to 1 (which corresponds to the very negative, smallest valid value because of the exponent bias). The result of the subroutine is returned through a special flag flip-flop. SPECIAL_VAL (J12): #1572 tmpA:exp -> sumreg:frac Handle special value #1573 jmp #1581 if not Sum register is zero Test exp for denorm #1574 except:denorm #1575 jmp #1577 if compare instruction No exception for comparison #1576 jmp #1571 if DE (denormalized) interrupt RNI if exception #1577 norm -> tmpA tag Handle denorm: tag empty? or valid? #1578 1 -> tmpA:exp Change to unnorm #1579 0 -> flag Clear flag #1580 return #1581 shift tmpA:frac L 0 bytes, 1 bits Shift to check if infinity vs NaN #1582 shift L -> sumreg:frac #1583 jmp #1579 if not Sum register is zero Clear flag for NaN #1584 1 -> flag Set flag for infinity #1585 return At #1581, the code checks if the value is infinity or NaN. Interestingly, this test isn't done directly, but by manipulating the value with the shifter. Recall that infinity has a significand of 10...00, while NaN has at least one additional 1 bit. The code shifts the significand one bit to the left; a zero result indicates infinity, while a nonzero result indicates NaN. As before, the result is returned in the flag flip-flop. ↩ The logic to compute the rounding bits is more complicated than described. There are two micro-instructions with slightly different behavior depending on the expConv value, but I won't get into that here. ↩ The ADJUST_PRECISION subroutine appears to return infinity if the significand overflows after rounding up, but I'm not entirely happy with this. For instance, 1.111... should round up to 2, not infinity; the significand overflows, but that's not an overflow of the float. Presumably, this gets fixed somewhere else. ↩ I don't know why Intel failed to document the feature that a condition code indicates whether a value was rounded up or down. The 8087 documentation is very thorough with corner cases; usually, when I find a strange circuit, I can find a line in the documentation that explains why it is there. Maybe the condition code feature was buggy, so it was easier to not document it? Maybe this feature was a hidden trap to catch competitors that copied the chip? (Intel had a secret instruction in the 8086 for this purpose, but NEC's version of the 8086 didn't have it, much to the disappointment of Intel's lawyers.) Maybe Intel wasn't sure if they wanted to support the feature in later versions? (This is why some of the 8085 processor's instructions weren't documented.) For now, it's a mystery. ↩

4 days ago 1 votes
I Am a Flat-Rate Monthly Responsibility Service

Most people, when asked why they do what they do, lie. This isn’t because they’re malicious but it’s because the honest justification for a career is rarely noble. It’s usually a combination of a decent paycheck, tolerable hours, and whatever neurosis you

5 days ago 3 votes
Super ultrawide Niri

A Niri workspace with 7 visible columns. After having used practically the same xmonad configuration for a decade and a half I’ve now modernized my setup with the scrollable-tiling Wayland compositor Niri. It’s been a bit of a struggle to unlearn my old workflow but I’m really growing to love Niri’s scrollable workflow, especially on my new super ultrawide display. Samsung Odyssey Neo G9 G95NC 57” My new 57” single monitor setup. What kicked off my Niri journey was the purchase of a new super ultrawide monitor. I bought the 57” Odyssey Neo G9 as it was the largest monitor I could find. (It’s marketed as a “gaming” display but it’s really an amazing productivity display.) It replaced my old 3-monitor setup: My old 3-monitor setup. The new display is wider so I had to move the speakers around 10–15cm further apart. I was debating whether to replace the center 31.5” monitor or replace all monitors with a single one but I think I made the right choice with the ultrawide. The curvature wasn’t an issue (I’ve come to prefer it) and the extra vertical space the portrait side monitors provided wasn’t as crucial as I thought. I think an ultrawide is worth it just to get rid of the annoying bezels. Small things can be a big thing sometimes. A more dynamic workflow xmonad and Niri are similar yet different. Both automatically lay out windows as you spawn them but xmonad (at least the way I used it) follows a layout algorithm that re-flows using a “master” window and combines the rest of the windows into one space, while Niri lays out windows in columns. The change is subtle but it implies that a new window won’t change the size of other windows. This is very nice if you spawn a lot of short-lived terminals or web browsers like I do and it reduces the amount of manual reshuffling I spend time on. My xmonad workflow was more static than my Niri one. In xmonad I made heavy use of workspaces, mapping ten workspaces mentally to different programs, such as 0 Firefox and 1 terminal logs on the left monitor; 3, 4 and 5 for different Neovim instances on the center monitor; 8 as chat and 9 for music or video on the right monitor. I had no rules to enforce this; it’s an emergent behaviour that served me well for years. With Niri it’s more dynamic. I still use workspaces but they no longer have direct shortcuts, I simply go up/down in the workspace list. Maybe I’ll add them in the future but with 3–4 workspaces that’s not as necessary. I spawn workspaces/windows when I need them and remove them when I’m done. Usually it’s one workspace per project (yes, I’m now one of those who have multiple up at once) with all the related things such as editor, terminals, and browser with docs. I don’t typically utilize the full screen width and I try to keep the things I’m working on in the center, often leaving 10–30% gaps on the sides. Even though I don’t normally use the “endless scrolling” feature of Niri I re-center selected windows all the time so I can look straight ahead as much as possible. Keyboard shortcuts As a fan of keyboard layouts of course I have to spend some time tinkering with good keyboard shortcuts (especially as Niri’s recommended keybinds don’t map well with my custom keyboard or custom layout). Navigation layer What I did was add a new navigation layer that’s enabled by holding Tab (ring + middle + index on the left-hand side) with all Niri related movement and layout keybinds. In the graphics above, all green-colored keys emit Gui (which gates all window manager commands) and you can see: Long press on Close Window to close a window. The long press requirement prevents accidentally closing windows. Arrows move through columns/windows. Long press resizes them. Workspace Up/Down focuses a different workspace. Center a column. Consume/Expel to combine windows into one column. (consume-or-expel-window-left/consume-or-expel-window-right) Expand Column makes a column take up all remaining space. (expand-column-to-available-width) Audio controls. To press them I release the index finger (keeping the ring and middle finger pressed to keep the layer active) and use the index to press the audio buttons. Mouse buttons. In Niri you can move floating windows with Gui + Left Mouse and Gui + Right Mouse to resize them. As my main mouse is a trackball integrated into the keyboard I had to add them to the left-hand side. I ended up using QMK’s customizable key repress feature that allows me to: Tab combo with my three fingers (layer is active) Release only the index (layer is still active, same as with the audio controls) Press the index again (now detects the press Gui + Left Mouse key down) Use the trackball to move the window And similarly for the right mouse button to resize with the middle finger. Works great! Because there are so many commands I want to send I placed Ctrl on the thumb that provides movement-related commands like so: For example: Arrows move columns/windows in the four directions. Move columns to the neighboring workspaces. Center visible columns. (center-visible-columns) Slightly different consume/expel semantics. (consume-window-into-column/expel-window-from-column) Regular keymaps These are triggered in the “normal” way by first pressing the Super combo and then another key on the base layer (I use autoshift so I shift with a long press). Window management Super + F toggle windowed fullscreen (keep column width) Super + Shift + F fullscreen window (over the entire display) Super + M maximize column (moves other columns) Run stuff Super + Enter terminal Super + E Noctalia’s launcher (also exists on the navigation layer as Launch) Super + S show Noctalia control center Super + Shift + S show Noctalia settings Super + Q power off monitors (they wake on input) Super + Shift + Q show Noctalia session menu (reboot etc) Super + Shift + L lock screen Misc Super + H show hotkey overlay Super + P interactive screenshot Super + Shift + P screenshot selected window Tweaks to the standard CachyOS setup In the process of moving from xmonad to Niri I also moved from Void Linux to CachyOS and I let the installer install Niri and give me a basic configuration together with Noctalia (that provides a statusbar, notifications, and a bunch of things you apparently need). Center the status bar and other Noctalia windows My centered Noctalia status bar. Feels absolutely required on this screen otherwise things end up in the corners. Firefox on XWayland Force Firefox onto XWayland as the Wayland popup manager is broken: environment { MOZ_ENABLE_WAYLAND "0" } Dead keys for Ghostty For some reason dead keys were broken in Ghostty. This is bad for me as the OS keyboard is set to Swedish and it uses them to type ~ (quite a crucial character for a programmer). The fix: environment { GTK_IM_MODULE "ibus" QT_IM_MODULE "ibus" XMODIFIERS "@im=ibus" } This needs ibus installed and running. Melange colorscheme Noctalia discovers custom color schemes under ~/.config/noctalia/colorschemes/<Name>/<Name>.json, so I dropped in my trusty Melange colorscheme there: { "dark": { "mPrimary": "#EBC06D", "mOnPrimary": "#292522", "mSecondary": "#A3A9CE", "mOnSecondary": "#292522", "mTertiary": "#85B695", "mOnTertiary": "#292522", "mError": "#D47766", "mOnError": "#292522", "mSurface": "#292522", "mOnSurface": "#ECE1D7", "mSurfaceVariant": "#34302C", "mOnSurfaceVariant": "#C1A78E", "mOutline": "#867462", "mShadow": "#1a1816", "mHover": "#E49B5D", "mOnHover": "#292522", "terminal": { "normal": { "black": "#867462", "red": "#D47766", "green": "#85B695", "yellow": "#EBC06D", "blue": "#A3A9CE", "magenta": "#CF9BC2", "cyan": "#89B3B6", "white": "#ECE1D7" }, "bright": { "black": "#34302C", "red": "#BD8183", "green": "#78997A", "yellow": "#E49B5D", "blue": "#7F91B2", "magenta": "#B380B0", "cyan": "#7B9695", "white": "#C1A78E" }, "foreground": "#ECE1D7", "background": "#292522", "selectionFg": "#C1A78E", "selectionBg": "#403A36", "cursorText": "#292522", "cursor": "#EBC06D" } } } Then pick the colorscheme: "colorSchemes": { "darkMode": true, "predefinedScheme": "Melange", "useWallpaperColors": false } Layout appearance The default appearance was pretty I admit but way too much blank space and weirdness. Some tweaks: layout { // Required for noctalia-shell to set wallpaper background-color "transparent" // Never auto-center focused columns (too much movement) center-focused-column "never" // But do center a single window always-center-single-column // No extra space around it all struts {} // No gaps between windows gaps 0 // The focus ring was annoying focus-ring { off } // Use a border with consistent width for all windows instead border { on width 2 active-color "#ebc06d" inactive-color "#403a36" } // Setting widths is important with such a large screen preset-column-widths { proportion 0.15 proportion 0.3 proportion 0.4 } default-column-width { proportion 0.15; } // Heights too, why not? preset-window-heights { proportion 0.15 proportion 0.5 proportion 1.0 } } // Prevent the mouse from opening the overview in the corners gestures { hot-corners { off } } Keep windows centered Niri has the always-center-single-column option, which is nice as I want to keep as much as possible in the center of the monitor when I’m working. But I very frequently use 2–3 smaller windows and with my frequent opening and closing I’d like them centered too. Luckily, Niri has an IPC you can use to make a small program that reacts to events and does this for you. I made a small rust project using the niri-ipc crate that does this for me: The autocenter implementation [dependencies] niri-ipc = "26.4.0" use std::collections::HashMap; use std::io; use niri_ipc::socket::Socket; use niri_ipc::{Action, Event, Request, Response, Window, Workspace}; #[derive(Clone, Copy, PartialEq, Eq, Hash)] struct WindowId(u64); #[derive(Clone, Copy, PartialEq, Eq)] struct WorkspaceId(u64); struct OutputName<'a>(&'a str); struct WindowState { workspace: Option<WorkspaceId>, width: f64, } fn main() -> io::Result<()> { let mut socket = Socket::connect()?; if !matches!(socket.send(Request::EventStream)?, Ok(Response::Handled)) { eprintln!("niri rejected event stream"); std::process::exit(1); } let mut known: HashMap<WindowId, WindowState> = HashMap::new(); let mut read_event = socket.read_events(); loop { let result = match read_event()? { // A full snapshot of the current state. Just refresh our state. Event::WindowsChanged { windows } => { known = windows .into_iter() .map(|w| { ( WindowId(w.id), WindowState { workspace: w.workspace_id.map(WorkspaceId), width: w.layout.tile_size.0, }, ) }) .collect(); Ok(()) } Event::WindowOpenedOrChanged { window } => { let workspace = window.workspace_id.map(WorkspaceId); let entry = WindowState { workspace, width: window.layout.tile_size.0, }; let prev = known.insert(WindowId(window.id), entry); match prev { // Don't center floats. _ if window.is_floating => Ok(()), // New window, try to re-center. None => maybe_center_new(&window), // Window changed workspace, try to re-center. Some(state) if state.workspace != workspace => center_focused_if_fits(), // Skip other things. Some(_) => Ok(()), } } Event::WindowClosed { id } => { if known.remove(&WindowId(id)).is_some() { center_focused_if_fits() } else { Ok(()) } } Event::WindowLayoutsChanged { changes } => { // Only re-center if the width was changed, otherwise our re-center will // loop back indefinitely. let mut resized = false; for (id, layout) in changes { if let Some(state) = known.get_mut(&WindowId(id)) { if (state.width - layout.tile_size.0).abs() > 0.5 { state.width = layout.tile_size.0; resized = true; } } } if resized { center_focused_if_fits() } else { Ok(()) } } _ => Ok(()), }; if let Err(e) = result { eprintln!("autocenter: {e}"); } } } /// Center a newly created window if the workspace is focused and if there's surrounding free space left. fn maybe_center_new(window: &Window) -> io::Result<()> { let Some(workspace_id) = window.workspace_id.map(WorkspaceId) else { return Ok(()); }; let Some(focused) = focused_workspace()? else { return Ok(()); }; if WorkspaceId(focused.id) == workspace_id { center_if_fits(&focused)?; } Ok(()) } /// Center windows in the focused workspace if there's surrounding free space left. fn center_focused_if_fits() -> io::Result<()> { if let Some(focused) = focused_workspace()? { center_if_fits(&focused)?; } Ok(()) } /// Center windows in the workspace if there's surrounding free space left. fn center_if_fits(workspace: &Workspace) -> io::Result<()> { let Some(output) = workspace.output.as_deref().map(OutputName) else { return Ok(()); }; let Some(width) = output_width(output)? else { return Ok(()); }; if workspace_width(WorkspaceId(workspace.id))? < f64::from(width) { center_visible_columns()?; } Ok(()) } /// Issue a one-shot query to Niri, wait, and return the response. fn query(request: Request) -> io::Result<Response> { match Socket::connect()?.send(request)? { Ok(response) => Ok(response), Err(msg) => Err(io::Error::other(msg)), } } /// Get the focused workspace. fn focused_workspace() -> io::Result<Option<Workspace>> { match query(Request::Workspaces)? { Response::Workspaces(ws) => Ok(ws.into_iter().find(|w| w.is_focused)), _ => Ok(None), } } /// Get the width of an output (monitor). fn output_width(name: OutputName<'_>) -> io::Result<Option<u32>> { match query(Request::Outputs)? { Response::Outputs(outputs) => Ok(outputs .get(name.0) .and_then(|o| o.logical.as_ref()) .map(|l| l.width)), _ => Ok(None), } } /// Calculates the width of all columns in the workspace. fn workspace_width(workspace_id: WorkspaceId) -> io::Result<f64> { let Response::Windows(windows) = query(Request::Windows)? else { return Ok(0.0); }; let mut columns: HashMap<usize, f64> = HashMap::new(); for w in windows { if w.workspace_id.map(WorkspaceId) != Some(workspace_id) { continue; } if let Some((col, _)) = w.layout.pos_in_scrolling_layout { let width = columns.entry(col).or_insert(0.0); *width = width.max(w.layout.tile_size.0); } } Ok(columns.values().sum()) } /// Send a command to center the visible columns. fn center_visible_columns() -> io::Result<()> { if let Err(msg) = Socket::connect()?.send(Request::Action(Action::CenterVisibleColumns {}))? { eprintln!("center-visible-columns rejected: {msg}"); } Ok(()) } One catch is that if a new window overflows the monitor width, the script won’t center the columns even if there would be free space left afterwards. This is a little weird but it’s consistent with Niri’s center-visible-columns command. I had a small itch to try to hack around it but in the end I left it alone… Is Niri worth it? Yes, absolutely. Niri has been a huge upgrade for me in combination with a single wide screen. My xmonad setup worked really well with three monitors—arguably a better fit in that context than Niri—but for the big-screen use-case Niri is superior. I’m curious how it holds up on my laptop, once I gather enough energy to install CachyOS on it… But that’s a side quest. The big-screen setup I spend most of my days in is the best I’ve ever had, and I have no desire to go back.

5 days ago 2 votes
📚 BoredReading

You seem to be enjoying this.

Join free to unlock everything.

Create free account

Already have an account? Sign in