Wednesday, September 7, 2022

Getting a little jumper-y

 This will be a short post. The tagline mentions jumper because this write-up is about reverse engineering the L2 cache jumpers on a 1990's Pentium (P54) motherboard. There are more jumpers that need some figuring out, and they will not appear until a future write-up.

While sorting through 'tech' I had stowed for future hobbies, I found myself unable to parrt with an AT (clone) case from what was once a 386DX/25 on which I first learned MS-DOS, Windows 3.1 and Microsoft Publisher.

That AT case no longer has anything from its "386" days except for the original two floppy drives and a power supply. At some point I had stashed a rescued Pentium motherboard in the case - Full Yes Incorporated SIS P5 PCI; the motherboard had 256K of 15ns cache  installed (eight chips) and a single cache chip with 25ns rating. The motherboard functioned only with cache disabled. 

With the cache disabled in BIOS, I uncovered another problem.  Since stashing the motherboard a decade ago, the Dallas CMOS RTC battery had faded. I was now greeted with a NMI error.  A site hinted that  bad CMOS can potentially interfere with being able to establish a healthy cache, resulting in memory errors and a dreaded NMI on boot. NMI errors are also reported as happening with bad disk blocks or a corrupted boot sector.

I ordered a new RTC from DigiKey, installed it, and my problems were not solved. 

Replacing the RTC allowed the CMOS to keep settings which meant the next challenge was to find out why I was getting the dreaded NMI.  The NMI ended up being a simple fix - either install parity SIMMs or disable the parity check in the BIOS.  I tried both ways, but I had yet another problem.  Upon getting to POST, the system hanged when the cache was enabled.

The motherboard uses a SIS 50x chipset. I read up on SIS chipsets and learned that most of the SIS motherboards supported 1MB to 2MB of cache.  This motherboard has 20 sockets for DIP32 cache chips.

I placed an order for 128Kx8's and upon receiving them, tested them in my Xgecu TL866ii to eliminate bad modules.

I installed the cache chips and the system would hang on POST with either a report of 0K or 64K of cache. I fussed with the jumpers in proximity to the cache sockets and found settings which claimed 2MB of alleged cache on POST; however, the system still hanged after POST or shortly after reaching a prompt in FreeDOS.

I read about how someone had managed to revise their system board - from the same era and same chipset - to accommodate 2MB of cache despite being manufactured with support for 1MB. That gave me incentive to pull out a multimeter and begin ohming connections on the system board.  I discovered that a block of jumpers positioned between the TAG RAM sockets and the CPU socket were not anything other than jumpers for specifying the TAG RAM configuration.  Up until this point I suspected they were settings for CPU or cache voltage.  Instead, neither.  Before filling all sockets, I double-checked that the TAG RAM sockets would accept 128Kx8's.  Other SIS system board manuals hinted that 1MB of cache with 128Kx8's requires a single 64Kx8 TAG SRAM.  Two banks of 1MB would follow to equal two 64Kx8's. 

Well, shoot; I only ordered 128Kx8's.

A 128Kx8 will address up to line A16, but what if the pin for A16 is neither connected to ground nor Vcc - in fact, not connected at all?  If that happened to be the case, I would need to short them ground; otherwise it might float between either of two 64K banks on the the 128Kx8 DIP32.  If they had been floating, that might explain the variance between 0K and 64K, or now with the jumpers configured to 2MB, wavering between 0K and 2MB.

A check with the multimeter showed line A16 of both TAG SRAM sockets was properly hooked to ground.  But the findings for the middle set of jumpers - JP7, JP8, JP9 and JP10 showed some measurements I did not immediately follow.  JP7, JP8 and JP10 all had been in a position to short to ground.  JP9 jumpered A13 to Vcc (+5V). The explanation for this became clear when I looked up the pin out for a DIP28 8Kx8.  The pin that feeds A13 on a 128Kx8 is actually the Vcc for an 8Kx8. From this I concluded that if I shifted JP7, JP8 and JP10 away from grounding, and JP9 away from Vcc, the configuration of twenty 128Kx8's would give me 2MB of fully functional cache.  Indeed it does.

I hope this helps someone else.

Here are the settings I established to configure a full 2MB cache using ISSI 61C1024-15Ns DIP32's

JP7  [32]1  means if 3-2-1 then [32] are jumpered and 1 is NC.  

JP8  [32]1  there are no markings for pin numbers so the order

JP9  [32]1  is purely based on JP4's position with respect to

JP10 [32]1  the other jumpers on the system board.


    [32]1 JP1

    [32]1 JP2

    [32]1 JP3

[54][32]1 JP4


That's all. Thanks for reading!



 

Thursday, February 10, 2022

Over my dead eMMc...

You find yourself in an impossible situation.  Impossible in that, it's never going to happen to you.

Until it happens.


You have a Chromebook that will not boot.  You discover the internal eMMC is defunct.  Cracking it open, you discover the inevitable truth; the eMMC is soldered on.  

After removing the WP screw, you succeed in preparing a flash drive that includes MrChromebox's BIOS for the Chromebook and a few moments later, it reboots and splashes a welcoming running rabbit.

Your favorite Linux distro now boots from a USB drive and a SD-CARD. But, you now have a new problem. 

The ex-Chromebook will not suspend because the internal eMMC can't be communicated with in full, and inhibits ACPI suspend.  

What now?

You learn by trial and error that after a warm reboot from a successful initramfs prompt, the internal  eMMC is temporarily out of the way, and suspend works.  

You now face the challenge of how to make that eMMC disappear without physical intervention.

You fiddle around with the init ramfs and arrive at a solution.  You realize you're no expert, but it suffices.

You make the business portion of this bundle communicate a shutdown to plymouth, which disrupts any cryptsetup (decrypt password prompts) and follow with a call to systemctl to start the shutdown.target.  An older iteration tried using pkill ^plymouth and a reboot command, but after realizing calling binaries directly is a tad abrupt, you fall back on using modernized system administrator techniques.

You drop this in /usr/lib/systemd/system

#  SPDX-License-Identifier: LGPL-2.1-or-later
#
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.

[Unit]
Description=Reboot the system if a more than one mmc is found.
Documentation=man:man
DefaultDependencies=no
After=systemd-udev-settle.service
Conflicts=cryptsetup.target
Before=lvm2-activation-early.service cryptsetup.target local-fs-pre.target dmraid-activation.service
Wants=systemd-udev-settle.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/lib/systemd/deadmmcreboot

[Install]
WantedBy=initrd.target

And you drop this in /usr/lib/systemd

#!/usr/bin/bash
# 2022-02-06 This ASUS C300 chromebook landing in my possession
# has a defunct internal eMMC. On cold boot, the eMMC can be
# read from the BIOS and even reaches a GRUB menu, and after
# the eMMC is enumerated by the Linux driver, a subsequent
# reboot appears to take the eMMC offline for future warm
# state reboots.  This helper attempts to detect if the eMMC
# is present and reboot at least once.
printMatch () {
    echo "Looks like this list qualifies for a reboot:"
    echo "  $(dmesg | grep mmcblk.:)"
  }
plymouthShutdown () {
    plymouth change-mode --shutdown
   }

for W in $(cat /proc/cmdline) 
  do
  IFS="="
  for X in ${W[@]}
    do 
    if [ ! -z $Y ]; then 
      if [ -e "/dev/${X}" ]; then
        printMatch
        plyouthShutdown
        systemctl start reboot.target
        fi
      unset Y
      fi
    if [ $X = "deadmmc" ]; then
      Y=1
      fi
    done
  done
# A crude way to determine if more than one mmc is detected.
# A fall-through in case the system is not configured with
# a kernel parameter.
# In the case of an ASUS C300 Chromebook with a defunct
# mmc, the kernel enumerates the internal eMMC as 
# mmcblk1
if [ ! -z "$(ls /dev/mmcblk0)" ] && [ ! -z "$(ls /dev/mmcblk1)" ]; then
  echo "Example: vmlinuz ... deadmmc=mmcblk1"
  printMatch
  plymouthShutdown
  systemctl start reboot.target
  fi

You create a directory 02deadmmcreboot under /usr/lib/dracut/modules.d.  Nearly done, you drop this in /usr/lib/dracut/modules.d/02deadmmcreboot

#!/usr/bin/bash

# called by dracut
check() {
    return 0
}

# called by dracut
depends() {
    return 0
}

# called by dracut
installkernel() {
    return 0
    local _arch=${DRACUT_ARCH:-$(uname -m)}

    instmods mmc_block sdhci_acpi sdhci_pci sdhci cqhci mmc_core
    hostonly="" instmods mmc_block mmc_core

    dracut_instmods -o -s ${_funcs} =drivers/mmc 
    return 0
}

# called by dracut
cmdline() {
    return 0
}

# called by dracut
install() {
    inst_simple "$systemdsystemunitdir"/deadmmcreboot.service
    inst_simple "$systemdutildir"/deadmmcreboot
    $SYSTEMCTL -q --root "$initdir" enable "deadmmcreboot.service"
}

Finally you run dracut.  
Because you weren't sure if this was going to work for all dead eMMCs, you remember that you could also try a boot parameter of deadmmc=mmcblk1 just in case, and add that to /etc/default/grub's default options.


Float this idea...

Since the early 1980's, it was known the floating bus in the Apple II would hold the value of a recently sampled value of memory displayed on screen.  When it comes to those who have taken advantage of the floating bus' availability, French Touch, deater (VMW), and John Brooks (VidHD) come to mind.  

One of the issues in using the floating bus is its availability across the various family of Apple II's.  On a //e, register $C022 will return a floating bus value, but on a IIGS it will not.  The IIGS and the //c use a number of the $C0xx IO addresses for newer purposes, and are otherwise unused in a II, II+ and //e.  

Mixed video modes are one of the neat things that can be made using the floating bus. I tested to see if I could reliably sync up the signal to provide any combination of mixed modes as seen in demos by French Touch and deater. I succeeded in making a program that reads the keyboard and based on user input, toggles the desired soft switch for the preferred mode while swapping every other scanline from page 1 and page 2.  Another program uses pages of 256-bytes to indicate any one of four IO registers to read, again, unique for page 1 and page 2.  Both work well enough for my exploration, but more ideas came to mind with regard to the floating bus.

I read that only if an expandable II series slots are not fully populated, it will reflect a recent video signal byte on the floating bus. If a slot is not populated, the address space for that card will be filled with floating bus values; but for the IIgs and some emulators, the floating bus is not exposed, rather just zeroes.  I have not tested if this also holds true for when the slot's expansion ROM space ($C800 - $CFFF).  For the sake of this exercise, slot 1 is vacant and on a //e, if the firmware is not enabled and the slot is vacant, $C100-$C1FF will take on the values of the floating bus.  This can be seen at a monitor prompt (CALL-151 from BASIC), and issuing C100L.

Slot ROM, either in the specific range, or in the expansion ROM address range, can it be used to invoke code? That question came to mind.

  • Can the displayed video contents of the floating bus be executed as code?

The answer is yes. There is a one caveat.  This caveat could be used to confound a person debugging code.  Not all instructions have a linear cycle count ratio to the number of bytes the instruction and data consume.  For example, a LDA $C030 (AD 30 C0) takes four cycles; however, the video refresh will pass over four bytes even though the instruction only consumes three.  A new question comes to mind.

  • What happens to the byte after the 2nd data byte of a LDA $absolute instruction?

At this time, I have not sought an answer to this question. I suspect the PC is now at the fifth byte and will direct the processor to that byte as the next opcode.

There is one another conditional caveat, and that is the hardware on which the scenario is applied.  On a physical enhanced Apple //e, the PC correctly fetches data for the instruction in at least some situations, such as a JMP (opcode $4C). In AppleWin 1.30, only zeroes are fetched for a JMP command. If at address $0000 a JMP $0300 (0: 4c 00 03), and if text page 2 is used for this example, and the text page contains all zeroes (800:0 m 801<800.bfem) except for address $800, and $800 has the JMP opcode (800: 4c), and the break vector is set to an address with a floating bus ($c030 on a //e), and text page 2 is actively displayed (can be lo-res but not hires), then if a BRK is executed (801g), the break vector redirects the PC to the value of $c030, which fetches the opcode (and data) from the floating bus.  If a BRK, the cycle continues. If not a BRK, the only other expected value is a JMP, which in AppleWin forms a JMP $0000, and in turn the JMP $0300 results.

On a physical //e, with slot 1 vacant, I filled text page 2 with a repeating pattern of AD 30 C0 EA EA EA EA EA except at $BF0 where I put in 4C 00 03.  I displayed text page 2 and invoked C100G.  In this exercise the BRK vector was set to an address that floats ($C050), which would not be used unless the invocation to C100G happened to land on the 00 in 4C 00 03 or some other value from the floating bus came up as 00 - say from the screen holes or during the blanking interval. The Apple //e ended up at the code at $300 every time.  This likely needs more in depth exploration as all the technical resources (Bishop's) describe that the blanking interval and RTZ can turn up some unexpected values.

That's all for now.  


Wednesday, November 24, 2021

Wake me up before you go-go or better yet, don't

In the few months I have noticed my Fedora notebook wake up in my backpack.  It has also awakened in the middle of the night and by morning the battery was nearly depleted. I first noticed the wake up when I paired another laptop with it.  When the other laptop powered up in to the operating system, my Fedora notebook would wake up.  I made a connection between wake on demand and bluetooth.

Initially I figured I had a device in the house that was paired with it.  I fired up Wireshark on the bluetooth adapter and found that every time that laptop awakened, there was a broadcast event from an Apple device. 

I began to suspect that I had an Apple device paired with it.  I turned off bluetooth from all Apple devices and it still awakened a few seconds later.

However, just the other day I found it awakened readily even far away from my house.

I repeated a Wireshark/bluetooth capture and sure enough, another Apple device.

I investigated wakeup parameters in the /sys tree and found this works to keep the laptop from waking on bluetooth events.

As root: find /sys/devices -name wakeup | grep usb2/2- | while read F; do echo disabled > $F; done

UPDATE 2021-12-31 - I have recently replaced the operating system with a newer release. I will watch and see if the issue persists.




Monday, November 22, 2021

I cannot hear my BUDDY

BUDDY is the system (board) name of an Acer Chromebase.

In September 2021, I procured a Acer Chromebase 24 CA24-CT as a wall-mounted organizational assistant and intercom.  

The project started out nicely with easy hardware upgrades, but went downhill sharply upon discovering that the bdw-rt5650 makes no sound in Linux operating systems.

I started a quest to figure out whether this was pre module (like using a module parm to load specific CODEC parms) or post module, say a mapping from the sound service, like ALSA.

Using my chromegraphy skills from years tinkering with prepping Windows laptops with compatible official builds, I loaded the official latest BUDDY build on to the Chromebase already having flashed in the full MrChromebox UEFI (tianocore) ROM. 

As usual, after preparing a fresh /dev/sda1 (STATE), the recovery screen began the 5 minute countdown, and I chose not to wait, although a person could wait out the timer and it would reboot having applied the necessary STATE changes.

The next step was to unverity the volume.  Using live meda, I did this by loading zram and setting the disksize module parameter (find /sys/devices -name disksize) to the same size as blockdev --getsize64 /dev/{device}3. Then I formatted the zram0, mounted it, mounted /dev/{device}3  "ROOT-A" read-only to a makeshift mount point for /dev/{device}3, rsync -av from /tmp/sda3 to zram0, umount both zram0 and {device}3, and dd from zram0 to /dev/{device}3.

This netted a modifiable ROOT-A and absent verity, means an easy mount from Linux.

The next step was to see if my distro would produce sound with the official Chrome kernel.

I grabbed the kernel modules (4.14.228-37743-g49d2c603c1a5) and nested them under /lib/modules of my Linux distro.  I then rebuilt the initramfs using dracut and attempted to boot my distro, and grabbed vmlinuz.A from the /syslinux directory of /dev/{device}12 "EFI-SYSTEM". I added a boot entry to (EFI) grub and tried to boot but grub presented that the kernel did not accept EFI handover

I switched to using the grub that exists on /dev/{device}12 "EFI-SYSTEM" but all I got was a blank screen - the screen right before the kernel slurps in the boot screen (chrome logo) from the ROOT-A file system.

I did some research and found that Chrome kernels do not use initrds.  Therefore, although the kernel command line has boot=local rootwait ro noresume noswap loglevel=7 noinitrd console= , perhaps I should not expect an initrd to be passed off to the kernel.

Nonetheless, I unpacked the initramfs using skipcpio and dumped the contents on to an ext2 partition. I pointed root=/dev/{device}17 in the kernel command line, but got the same results

I then went to contemplate other ways I might verify if the sound is tied up on the kernel or a service.  I remembered Cloudready, and shimmed in the official kernel with Cloudready expecting sound.  

The shimming required again using the official Chrome grub loader to deal with booting the official non EFI kernel on the UEFI BIOS from MrChromebox.

Alas, no sound.  The same holds true for the 5.x kernel that Cloudready uses - no sound.  I verified that alsa was using the cras service and although there is volume control, in the Chromium setup interface, neither the welcome chime is heard, nor any sound curves "right parens" on the speaker symbol respective to the current volume level.

At this point, it suggests that the absence of sound is not likely a problem with the module or module parms, but potentially the sound mapping in a sound service.  Let's hope.  


UPDATE: November 24th, 2021

The driver in Linux works. In Fedora 35, the /usr/share/alsa/ucm directory is empty and the ucm2 directory has sound device configurations. By grabbing the bdw-rt5650 configurations from the /usr/share/alsa/ucm directory of the most recent recovery image*, and adding a header of Syntax 4 to the bdw-rt5650.conf file, then restarting pipewire-session-manager**, I was able to use aplay*** and hear sounds.  The first time the sounds were quiet, so before testing with aplay, invoke alsamixer and press F6 to switch to the bdw-rt5650 and max the master and speaker volumes.

* As root: modprobe zram; echo $(unzip -l chromeos_12239.92.0_codename.zip | grep "1 file" | cut -d' ' -f1) > $(find /sys/devices -name disksize); zcat chromeos_12239.92.0_codename.zip | dd of=/dev/zram0; losetup /dev/loop4 -P /dev/zram0; mkdir /tmp/root-a; mount /dev/loop4p3 /tmp/root-a -o ro; cp -av /tmp/root-a/usr/share/alsa/ucm /usr/share/alsa; umount /tmp/root-a; losetup -d /dev/loop4; rmmod zram

** systemctl --user restart pipewire-session-manager

*** aplay -D $(aplay -L | grep ^sysdefault) $(find /usr/share -name *.wav)

This does not produce sound through firefox/youtube, but the restart does appear to reload alsa's device configuration.  In order to hear sound through firefox/youtube, I duplicated Speaker details in HiFi.conf (of the same directory as bdw-rt5650) to match with another config.  Though the sound through firefox/youtube was extremely quiet, direct alsa sound was loud and clear.

Saturday, October 30, 2021

Fedora 34 live and wine

I am a fan of live media. When I am not feeling adventurous I leverage an XFCE spin of Fedora.  The release as of writing is Fedora 34 x86_64. 

I depend on a Windows program in my mostly Linux-only lifestyle, and wine makes this possible.  When dabbling with this dependency in conjunction with live media, I discovered I ran out of RAM-backed file system space.

I am not versed in how Fedora yields a RAM-backed file system overlay. From a cat /proc/filesystems, it does not appear to use overlayfs. overlayfs is not in the list. Does it manifest in the pivot through /run/initramfs?  I am unsure, and will not explore or find the answer in this post. 

What I need is a way to dnf -y install wine.  

After two other rounds of trial and error, I got this foundation script. It does not check or handle errors, but in essence, automates the steps necessary to get wine installed so I can use my Windows program.

My laptop has 32GB of RAM, and in my experience, the modern live images demand a minimum of 4GB of RAM to do the very minimum and with web browsing out of the question.

The cheatiest angle - (a new word for me => cheatiest) - is to make more space on /usr since that is where most libraries and binaries will end up.  I did not evaluate if any directory below /usr (e.g. /usr/share or /usr/lib) is a better candidate because, as said, cheatiest.

Now that you know I target /usr, the overall size of /usr hovers slightly above 4 GB uncompressed.  It should be an appreciable amount less than 4 GB when subjected to the fast compression used by the zram module.  The overall size of /usr (df -h) with wine installed, registers slightly above 6.3 GB.  This means my cheatiest approach is expecting at least 8 GB of installed RAM.  There is ample room for improvement, yet this solves my need for a live system where I can use my Windows program without concern of a computer's primary purpose.

EDIT: updated on 11/4/2021. 

Here's the code. 

#!/bin/sh
sudo swapoff /dev/zram0
sudo rmmod zram
sudo modprobe zram num_devices=8
sudo find /sys/devices -name disksize -exec chmod 777 {} \;
find /sys/devices -name disksize | while read F; do echo $((16384*1048576)) > ${F}; done
sudo mkfs.ext4 /dev/zram0
sudo mkdir /tmp/0
sudo mount /dev/zram0 /tmp/0
cd /usr
sudo cp -av ./* /tmp/0
sudo setenforce permissive
sudo umount /tmp/0
sudo mount /dev/zram0 /usr
host dns.google || ping -c 1 dns.google || read -p "Check your internet connection...press ENTER when solved."
host dns.google && ping -c 1 dns.google && echo "Looks good."
host dns.google || ping -c 1 dns.google || echo "No good. Restart this script." || exit 1
sudo dnf -y install wine
echo "That should have worked without running out of file system space."
echo "But for reasons (unknown to me) doing the same kind of effort by "
echo "first dd'ing the live image (using its dev loop to a zram dev)"
echo "and cp -av'ing the file system over to a larger ext4 formatted"
echo "ext4 fs, did not work when mounting to root.  It shows the mount"
echo "as being updated, but df -h does not; rather it shows the previous"
echo "space from the live mount. Again, I do not understand how the live"
echo "mount provides the compressed fs image, and subsequently, gives a "
echo "RAM overlay."
cd ~
mkdir .wine
sudo mount tmpfs -t tmpfs $(pwd)/.wine -o exec,uid=1000
wine cmd


Sunday, February 14, 2021

A method to relocate a BASIC program

On an Apple II with ROM BASIC, a BASIC program can run anywhere there is space in memory with the caveat that one step must be performed or the NEW command might fail. The first byte of the fresh location where BASIC will run must be set to 0. 

BASIC initially locates programs at $800. The decimal conversion of $800 is 2048.  To demonstrate the problem caused by the first byte, enter a fresh BASIC session. Issue the following commands:

] NEW
] LIST

] POKE 2048,255
] NEW
?SYNTAX ERROR 

] POKE 2048,0
] NEW

* no syntax error is encountered

] REM $0C00 IS 3072 IN DECIMAL
] POKE 3072,255
] REM $0C IS THE HIGH BYTE AND IS 12 IN DECIMAL
] POKE 104,12
] NEW
? SYNTAX ERROR

] POKE 3072,0
] NEW

* no syntax error is encountered


This is fantastic. A program can be located above the TEXT PAGE 2 ($0800-$0BFF) and use it for LORES graphics.  But, these steps must be performed each new session.

] REM START BASIC ABOVE GR PAGE 2 AT $0C00
] POKE 104,12
] POKE 3072,0

Could this be automated if loading from DOS or ProDOS? 
If yes, could it be automated per each program?

The short answer is yes & yes. The answer is provided in part by left over, or stale, data in memory.  I think of that state of data like dinosaur bones. That data is a sign of what happened in the past and may only be interesting to a few people. 

Retrieving that data in a BASIC program can be performed by using the PEEK command provided some awareness to where the data resides. If the data happens to be ASCII text, that data can be exposed to BASIC by using the POKE command to alter an already defined string.  I will demonstrate this later.

For my purpose, I will use the input buffer of ProDOS stored at $BCBD and the input buffer length at $BCBC.

Process Background
BASIC uses the zero page when operations are performed on variables.  There are dinosaur bones of BASIC variables found in the zero page. In particular, a region identified as scratch registers by Jon Relay's* very useful Apple II references.

Via trial and inspection, I discovered that scratch registers $85/$86 and $8C/$8D are affected when a new string is assigned the value of an old string.  In my example, N$ is the new string and O$ is the old string.

This assignment leaves dinosaur bones in the zero page, specifically the most recently used addresses of the strings.
N$ = O$

In this assignment, $85/86 is the location in memory of the old string and $8C/$8D is the location of the new string.

$85 = low address of old string
$86 = high address of old string

$8C = low address of new string
$8D = high address of new string

In the references* mentioned above, a keen reader will notice that the last used variable address is found in memory at $83/$84.  In the assignment operation N$ = "ascii text", $83/$84 is very much equal to $85/$86, and is the primary last used variable address and not one of the scratch registers.  
 
$83 = 131 in decimal
$84 = 132 in decimal

At first glance, this might appear to provide direct access to the string variable:
A = PEEK(131)+PEEK(132)*256

However, capturing the address of the numerical variable is not directly possible with a numerical variable. The address of the numerical variable receiving the value is assigned to $83/$84 and $85/$86 regardless of any operations on the right side of the assignment.  The value stored in the new variable is the address of itself, and not the anticipated address of the string.

Addresses $83 ... $86 will not keep value when capturing the last used variable address as a numerical variable. 

Lucky for us, the knowledge of how $8C/$8D store the last used variable addresses can help in this situation. 

Assigning a new string with the value of the old string, and even self assignment like O$ = O$ will suffice and keep a copy of the address of O$ in $8C/$8D. 

If the next operation is as follows, the address to the string is obtained:
A = PEEK(140)+PEEK(141)*256

Now for a word about how the string is stored.  The string payload, as in the ASCII text, is not located inline with the variable. The byte stored at A is the string length.  A+1 and A+2 are a pointer to the absolute locations of the string.  A+1 is the low byte and A+2 is the high byte of the string payload pointer.

We have the location of the string length and the pointer. With both these pieces of information, we can resize and repoint the string to the name of the program. 

This will be useful if we RUN THE.PROGRAM to get it loaded at our desired BASIC location.  If at first your LOAD the program, a RUN will not succeed.  

If the intent is to LOAD the program, this method is not needed as a the POKE statements on lines 10 and 20 can be issued before the LOAD.  This code that follows takes care of loading the BASIC program to the desired location.   

10 IF PEEK(104) <> 8 GOTO 50

20 POKE 12 * 256,0: POKE 104,12

30 A$ = “/”: A$ = A$: A = PEEK(140)+PEEK(141)*256: POKE A,PEEK(48316): POKE A+1,189:POKE A+2,188

40 NA$ = A$: PRINT CHR$(4);”LOAD”;NA$


Line 10 checks if the BASIC program location is at the default.  If it has already been altered, skip to program execution.
Line 20 clears the problematic first byte of the relocation address and then sets the high byte of the BASIC program location to 12 ($C00). 
Line 30 defines A$, performs the assignment that leaves dinosaur bones in $8C/$8D, stores the address from $8C/$8D in the variable A, pokes the value of 48316 ($BCBC) to the string, and closes by setting the high and low bytes of the ProDOS keyboard input buffer to A+1 and A+2.
Line 40 performs an assignment to copy the string, and invokes a ProDOS BASIC LOAD of the file.

There and done.

More on Variable Zero Page registers
In my experience, the last used variable address pointer in the zero page is only valid immediately after a variable has received an assignment.  Further instructions may clobber it.  Take this derivative of the code above which is not relocated:

30 A$ = “/”: A$ = A$: A = PEEK(140)+PEEK(141)*256: POKE A,PEEK(48316): POKE A+1,189:POKE A+2,188

40 NA$ = A$

50 PRINT PEEK(131)+PEEK(132)*256

60 GOTO 60


The output is 2163 from line 50, which in fact points to the original A$ having the updated paload pointer to $BCBD, but as of executing the GOTO in line 60, that value of decimal addresses 131/132 ($83/$84) are now $824, somewhere in the middle of the BASIC code.  The scratch registers at $85/$86 point to $881 and scratch registers $8C/$8D point to $009D.  No time is spent on $8C/$8D since the pointer destination fails to correspond to any string payload.  $881, however, points to the string region $95F7.  Inspection of the Start of string storage $6F/$70 yields $95F7, confirming a viable region in which BASIC is allowed to provision string payloads. The payload for the $NA assigned in line 40 and located in memory at $95F7 has the ASCII string RUN (CTRL-M) and substantiates that the variable assignment at line 40 successfully copied the data from the $BCBD text input buffer.


This hopefully stresses the importance of fetching the payload pointer immediately after performing an assignment that populates $8C/$8D.


Thanks for reading.