Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Universal bootloader #4

Merged
merged 3 commits into from
Feb 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 175 additions & 0 deletions Packages/UniversalBootloader/EEPROM.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
bootloader = {
init = nil,
port = 8,
srv = "/srv",
storageMounted = false,
}

local fs = filesystem

function bootloader:mountStorage(searchFile)
if self.storageMounted then
return
end

fs.initFileSystem("/dev")

local devs = fs.children("/dev")
for _, dev in pairs(devs) do
local drive = filesystem.path("/dev", dev)
fs.mount(drive, self.srv)
if searchFile == nil or self:programExists(searchFile) then
self.storageMounted = true
return true
end
fs.unmount(drive)
end

return false
end

function bootloader:programExists(name)
local path = filesystem.path(self.srv, name)
return filesystem.exists(path) and filesystem.isFile(path)
end

function bootloader:loadFromStorage(name)
if not self.storageMounted then
self:mountStorage()
end

if not self:programExists(name) then
return nil
end

fd = fs.open("/srv/"..name, "r")
content = ""
while true do
chunk = fd:read(1024)
if chunk == nil or #chunk == 0 then
break
end
content = content .. chunk
end
return content
end

function bootloader:initNetBoot()
if self.netBootInitDone then
return
end
self.net = computer.getPCIDevices(classes.NetworkCard)[1]
if not self.net then
error("Net-Boot: Failed to Start: No Network Card available!")
end
self.net:open(self.port)
event.listen(self.net)

-- Wrap event.pull() and filter Net-Boot messages
local og_event_pull = event.pull
function event.pull(timeout)
local args = {og_event_pull(timeout)}
local e, _, s, p, cmd, programName = table.unpack(args)
if e == "NetworkMessage" and p == self.port then
if cmd == "reset" and programName == self.init then
computer.log(2, "Net-Boot: Received reset command from Server \"" .. s .. "\"")
if netBootReset then
pcall(netBootReset)
end
computer.reset()
end
end
return table.unpack(args)
end
self.netBootInitDone = true
end

function bootloader:loadFromNetBoot(name)
if not self.netBootInitDone then
self:initNetBoot()
end
self.net:broadcast(self.port, "getEEPROM", name)
local program = nil
while program == nil do
local e, _, s, p, cmd, programName, code = event.pull(30)
if e == "NetworkMessage" and p == self.port and cmd == "setEEPROM" and programName == name then
print("Net-Boot: Got Code for Program \"" .. name .. "\" from Server \"" .. s .. "\"")
return code
elseif e == nil then
computer.log(3, "Net-Boot: Request Timeout reached! Retry...")
break
end
end
return nil
end

function bootloader:loadCode(name)
if not self.storageMounted then
computer.log(0, "Mounting storage")
self:mountStorage()
end

local content = nil
if self.storageMounted then
computer.log(0, "Loading " .. name .. " from storage")
content = self:loadFromStorage(name)
else
computer.log(0, "No storage available")
end

if not content then
computer.log(0, "Loading " .. name .. " from net boot")
content = self:loadFromNetBoot(name)
end

return content
end

function bootloader:parseModule(name)
local content = self:loadCode(name)
if content then
computer.log(0, "Parsing loaded content")
local code, error = load(content)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, you can pass the module name to the load function to enhance tracebacks even further:

Suggested change
local code, error = load(content)
local code, error = load(content, name)

if not code then
computer.log(4, "Failed to parse " .. name .. ": " .. tostring(error))
event.pull(2)
computer.reset()
end
return code
else
computer.log(3, "Could not load " .. name .. ": Not found.")
return nil
end
end

function bootloader:loadModule(name)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think loadModule should check if a module with this name was already loaded (or in progress of loading), and skip it. Otherwise, a module might be loaded twice if a user manually calls bootloader:loadModule("display.lua").

computer.log(0, "Loading " .. name .. " through the bootloader")
local code = self:parseModule(name)
if code then
-- We don't really expect this to return
computer.log(0, "Starting " .. name)
local success, error = pcall(code)
Copy link

@taminomara taminomara Jan 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using xpcall to display proper traceback:

Suggested change
local success, error = pcall(code)
local success, error = xpcall(code)
if not success then
computer.log(3, error.message.."\n"..error.trace)

if not success then
computer.log(3, error)
event.pull(2)
computer.reset()
end
else
computer.log(4, "Failed to load module "..name)
end
end

function bootloader:main()
if not self.init then
self.init = computer.getInstance().nick
end
if not self.init then
computer.log(4, "No init program set")
computer.stop()
end
self.init = string.gsub(self.init, "#.*$", "")
computer.log(1, "Booting " .. self.init)
self:loadModule(self.init)
end

bootloader:main()
34 changes: 34 additions & 0 deletions Packages/UniversalBootloader/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Universal Bootloader

This package implements a bootloader that will load scripts using several different
methods.

## How to specify which script to load

You can specify the script to load in two ways:

* Change the nick of the computer cabinet to the name of the script you want to
run. Anything after a `#` character in the nick will be ignored.
* Edit the bootloader code to set `init = "name-of-script.lua"` at line 2.

## How the bootloader looks for the script

The bootloader will first look for any drives in the cabinet. If a drive is
found containing the file specified then it will load the file from that drive.

If no drive is found containing the requested file then the file will be requested
from the network using the net-boot protocol on port 8.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can also add a link to the net-boot package [net-boot protocol](/package/NetBoot)


## Loading multiple modules

If you want to split your scripts up into multiple Lua files then you can use
the bootloader to load them. Suppose you your code split into two files,
`main.lua` and `display.lua`. Then you can start `main.lua` with:

```lua
bootloader:loadModule("display.lua")
```

This will load and execute `display.lua`. Note that `display.lua` should only
contain declarations; it should (normally) run and end once all the declarations
are complete so that the code in `main.lua` can then run.
5 changes: 5 additions & 0 deletions Packages/UniversalBootloader/metadata.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name = "UniversalBootloader"
version = "1.0"
short_description = "Bootloader that uses both storage and net-boot"
tags = ["boot", "network"]
authors = ["tomkcook"]
2 changes: 1 addition & 1 deletion README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ It contains templates, full-fledged programs and examples provided by the mod's

Anyone with a GitHub account can publish a package.

Create fork of this git repository and add a folder with an alphanumeric name (`[a-zA-Z0-9_\-]+`) to the link:/Repository[`Repository`] folder.
Create fork of this git repository and add a folder with an alphanumeric name (`[a-zA-Z0-9_\-]+`) to the link:/Packages[`Packages`] folder.

In this newly created folder create a `README.adoc` or `README.md` containing a description of your package. +
Add a `EEPROM.lua` file containing your EEPROM Code. +
Expand Down