Bar
SpaceWire UK
Specialist providers of VHDL Intellectual Property & Design Services
BarBarBarBar
Tutorial
Missing Image!
Part 28 - Reload Operating System via the Webserver

Introduction

This tutorial details the steps required to add server-side capability for reloading the Operating System, this will work in a very similar fashion to the way the Firmware is reloaded. By placing the Operating System BOOT files locally on the SD-Card, as with the Firmware files, these can be used to reload the OS without the need for client-side scripts to be executed. The operation will contain similar functionality to that of the swuk_upload_boot script. Also detailed in the tutorial are the steps required to add server-side capability for loading Peek & Poke configuration files from the SD-Card.

Aims

The aims of this tutorial are as follows :-

    Part 1 - Setup environment

    1. Pre-requisites

    Part 2 - Copy BOOT & Configuration files to the SD-Card

    1. Copy the files

    Part 3 - Update the Webserver files

    1. Create an Operating System reload script
    2. Update Website header
    3. Update System webpage
    4. Update System Javascript
    5. Update Peek & Poke webpage
    6. Update Peek & Poke Javascript

    Part 4 - Check everything is working as expected (beta)

    1. Patch Zedboard PetaLinux v17.0
    2. Reload OS into Zedboard PetaLinux v16.0
    3. Patch Zedboard PetaLinux v16.0
    4. Reload OS into Zedboard PetaLinux v17.0
    5. Patch Zedboard PetaLinux v17.0
    6. Reload Firmware
    7. Patch Zedboard PetaLinux v17.0
    8. Load Peek & Poke Configuration

    Part 5 - Make the changes permanent

    1. Bump version
    2. Update Website BitBake recipe
    3. Update the auto-start application

    Part 6 - Revision Control

    1. Commit new & updated files
    2. Create production release

    Part 7 - Check everything is working as expected (alpha)

    1. Copy BOOT files to the SD-Card
    2. Patch Zedboard PetaLinux v17.0
    3. Reload the Operating System using the Webserver
    4. Quick functionally check

    Part 8 - Final resulting build file(s)

    1. Resulting BOOT files
    #### Part 1 - Setup environment ####

    1. Pre-requisites

    Starting from this point is possible but requires a few pre-requisites.
    #### Part 2 - Copy BOOT & Configuration files to the SD-Card ####

    2. Copy the files

    Recursively copy all the PetaLinux BOOT files to the SD-Card.
    steve@Desktop:~/swuk_tutorial$ swuk_ssh 192.168.2.87
    steve@Desktop:~/swuk_tutorial$ sshpass -p petalinux scp -r ~/Documents/swuk_tutorial/petalinux petalinux@192.168.2.87:/media/sd-mmcblk0p1
    Recursively copy all the Configuration files to the SD-Card.
    steve@Desktop:~/swuk_tutorial$ sshpass -p petalinux scp -r ~/Documents/swuk_tutorial/configuration petalinux@192.168.2.87:/media/sd-mmcblk0p1
    #### Part 3 - Update the Webserver files ####

    3. Create an Operating System reload script

    Creat a CGI (bash) script that performs the following :-
    1. Backup the 3 original BOOT files.
    2. Replace the BOOT files with new ones.
    3. Reboot the system.
    steve@Desktop:~/swuk_tutorial$ subl zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/reload_os.cgi

    reload_os.cgi

    #!/bin/sh
    
    #
    # File .......... reload_os.cgi
    # Author ........ Steve Haywood
    # Website ....... http://www.spacewire.co.uk
    # Project ....... Zedboard Linux (SpaceWire UK Tutorial)
    # Date .......... 13 Mar 2026
    # Version ....... 1.0
    # Description ...
    #   Simple CGI to reload the Operation System :-
    # 1. Backup the 3 original BOOT files.
    # 2. Replace the BOOT files with new ones.
    # 3. Reboot the system.
    #
    
    # Declarations
    declare -r c_root=/media/sd-mmcblk0p1             # Str: SD-Card location
    declare -r c_peta=petalinux                       # Str: Boot files parent directory
    declare -r c_boot=${@}                            # Str: Boot files subdirectory
    declare -r c_bfiles=(BOOT.BIN boot.scr image.ub)  # Str: Boot files
    declare    bfile                                  # Str: Boot file
    
    # Output Header
    printf "Content-type: text/html\n\n"
    
    # Check for single argument
    [ ${#} -ne 1 ] &&
      echo -n "Wrong number of arguments!" &&
      exit
    
    # Check for directory existence
    [ ! -d ${c_root}/${c_peta} ] &&
      echo -n "Boot files directory (${c_root}/${c_peta}) not found!" &&
      exit
    
    # Iterate through list of boot files to check for existence
    for bfile in ${c_bfiles[@]}; do
      [ ! -f ${c_root}/${c_peta}/${c_boot}/${bfile} ] &&
        echo -n "Boot file (${bfile}) not found!" &&
        exit
    done
    
    # Iterate through list of boot files to backup & replace
    for bfile in ${c_bfiles[@]}; do
      mv ${c_root}/${bfile} ${c_root}/${bfile}.bak
      cp ${c_root}/${c_peta}/${c_boot}/${bfile} ${c_root}
    done
    
    # Reboot system
    /sbin/reboot
    
    # Signal success
    echo -n "OK"
    
    Direct download available here :-
    steve@Desktop:~/swuk_tutorial$ wget https://spacewire.co.uk/tutorial/shared/repos/0035/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/reload_os.cgi -O zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/reload_os.cgi
    Make the script executable.
    steve@Desktop:~/swuk_tutorial$ chmod +x zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/reload_os.cgi

    4. Update Website header

    Update the header to include the following changes :-
    steve@Desktop:~/swuk_tutorial$ subl zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/header.php

    header.php

    <?php
    //
    // File .......... header.php
    // Author ........ Steve Haywood
    // Website ....... http://www.spacewire.co.uk
    // Project ....... Zedboard Linux (SpaceWire UK Tutorial)
    // Date .......... 13 Mar 2026
    // Version ....... 2.0
    // Description ...
    //   Header include for website pages.
    //
    ?>
    
    <?php
    
    // Menu Key = Subdirectory Name
    const c_tab      = 0; // Menu Name
    const c_title    = 1; // Page Tile & Header Text
    const c_onload   = 2; // OnLoad Calls
    const c_submenu  = 3; // Submenu Array
    // Sub-Menu Key = Webpage
    const c_sub_tab  = 0; // Menu Name
    
    // Define pages
    $pages = array(
      "home" => array (
        "Home",
        "Home",
        "",
        array ()
        ),
      "system" => array (
        "System",
        "System Information &amp; Operating System / Firmware Load",
        "",
        array ()
        ),
      "peekpoke" => array (
        "Peek &amp; Poke",
        "Peek &amp; Poke Addresses",
        "",
        array ()
        ),
      "zedboard" => array (
        "Zedboard",
        "Interactive Zedboard",
        "",
        array ()
        ),
      "misc" => array (
        "Misc",
        "Miscellaneous",
        "",
        array (
               "test-cgi" => array("CGI Basic Test"),
               "hello_world.php" => array("PHP Basic Test"),
               "sqlite_test.php" => array ("SQLite Basic Test"),
               "phpliteadmin.php" => array("PHP Lite Admin"),
              ),
        )
    );
    
    // Get subdirectory webpage is being served from
    $base = basename(dirname($_SERVER['PHP_SELF']));
    ?>
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <?php
    // Add global CSS if one exists
    if (file_exists("../share/style.css")) {
        echo '<link href="../share/style.css" rel="stylesheet">';
    }
    // Add local CSS if one exists
    if (file_exists("style.css")) {
        echo '<link href="style.css" rel="stylesheet">';
    }
    ?>
    <title><?php echo $pages[$base][c_title] ?></title>
    </head>
    <body onload="<?php echo $pages[$base][c_onload] ?>">
    <div class="section"><h2><?php echo $pages[$base][c_title] ?></h2></div>
    
    <div class="menu">
    <ul class="menu_ul">
    <?php
    foreach ($pages as $key => $fields) {
      if ($key == $base) {
        $colour = "000";
      } else {
        $colour = "666";
      }
      if (count($fields[c_submenu]) > 0) {
        echo '<li class="menu_li"><a style="color:#'.$colour.'" href="#">'.$fields[c_tab].'</a>';
        echo '<ul class="menu_ul">';
        foreach ($fields[c_submenu] as $subkey => $subfields) {
          echo '<li class="menu_li"><a href="/cgi-bin/'.$subkey.'" target=_blank>'.$subfields[c_sub_tab].'</a></li>';
        }
        echo '</ul>';
        echo '</li>';
      } else {
        echo '<li class="menu_li"><a style="color:#'.$colour.'" href="../'.$key.'">'.$fields[c_tab].'</a></li>';
      }
    }
    ?>
    </ul>
    </div>
    
    Direct download available here :-
    steve@Desktop:~/swuk_tutorial$ wget https://spacewire.co.uk/tutorial/shared/repos/0035/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/header.php.txt -O zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/header.php
    Check out the changes.
    steve@Desktop:~/swuk_tutorial$ git difftool zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/header.php

    5. Update System webpage

    Update the System webpage to include the following changes :-
    steve@Desktop:~/swuk_tutorial$ subl zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/system/index.php

    index.php

    <?php
    //
    // File .......... index.php
    // Author ........ Steve Haywood
    // Website ....... http://www.spacewire.co.uk
    // Project ....... Zedboard Linux (SpaceWire UK Tutorial)
    // Date .......... 13 Mar 2026
    // Version ....... 11.0
    // History .......
    //   9.0 zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/index.php
    //   7.0 zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/index.cgi
    // Description ...
    //   Webpage for System Information & Operating System / Firmware Load.
    //
    ?>
    
    <?php
    // Get information
    $sys_host = exec('hostname', $retval);
    $sys_time = exec('date', $retval);
    $sys_load = exec('awk \'{print $1}\' /proc/loadavg', $retval);
    $sys_up = exec('awk \'{print $1}\' /proc/uptime', $retval);
    $cpu_model = exec('grep model /proc/cpuinfo | cut -d : -f2 | tail -1 | sed \'s/\s//\'', $retval);
    $cpu_cores = exec('grep -c ^processor /proc/cpuinfo', $retval);
    $mem_total = (int)((int)exec('free | awk \'NR==2{print $2}\'', $retval) / 1024);
    $mem_used = (int)((int)exec('free | awk \'NR==2{print $3}\'', $retval) / 1024);
    $mem_free = (int)((int)exec('free | awk \'NR==2{print $4}\'', $retval) / 1024);
    $net_mac = exec('cat /sys/class/net/eth0/address', $retval);
    $net_ip_loc = exec('ip a | grep inet | grep -vw lo | grep -v inet6 | cut -d \/ -f1 | sed \'s/[^0-9\.]*//g\'', $retval);
    $net_ip_ext = exec('wget -q -O- http://ipecho.net/plain', $retval);
    ?>
    
    <?php require '../share/header.php'; ?>
    
    <div class=content>
    
    <div class=inner>
    <?php
    $tables = array("Operating System", "Firmware");
    $id = array("os", "fw");
    $fields = array("Description", "Company", "Author", "Version", "Timestamp", "Hash");
    for ($table = 0 ; $table < count($tables) ; $table++) {
    echo "
    <table>
    <tr>
    <th colspan=2>$tables[$table] Information</th><th><button id=$id[$table]_read>Read</button></th>
    </tr>
    ";
    for ($field = 0 ; $field < count($fields) ; $field++) {
    echo "
    <tr>
    <td style='text-align:right'>$fields[$field]</td>
    <td style='text-align:left' id=$id[$table]_id_$field>Unknown</td>
    <td><img id=$id[$table]_id_status_$field style='vertical-align:middle' src='../share/amber.gif' title='Unknown!' alt='Missing Image!'></td>
    </tr>
    ";
    }
    echo "
    </table>
    ";
    }
    ?>
    </div>
    
    <div class=inner>
    <?php
    for ($table = 0 ; $table < count($tables) ; $table++) {
    $colspan = ($table == 0) ? 6 : 5;
    echo "
    <table>
    <tr><th colspan=$colspan>Reload $tables[$table]</th></tr>
    <tr>
    <td style='text-align:right'>Select</td>
    <td style='text-align:left'>
    <select id=$id[$table]_select>
    <option value=none>N/A</option>
    ";
    $location = ($table == 0) ? '/media/sd-mmcblk0p1/petalinux/*' : '/media/sd-mmcblk0p1/firmware/*.bin';
    foreach(glob("$location") as $file) {
      if (($table == 0) ? is_dir($file) : is_file($file)) {
        $basename = pathinfo($file)["basename"];
        $filename = ($table == 0) ? $basename : pathinfo($file)["filename"];
        echo "<option value=$basename>$filename</option>";
      }
    }
    $count = ($table == 0) ? "<td id=$id[$table]_count>0/120s</td>" : "";
    echo "
    </select>
    <td id=$id[$table]_action>Idle</td>
    </td><td><progress id=$id[$table]_progress value=0 max=120></progress></td>
    $count
    <td><img id=$id[$table]_status style='vertical-align:middle' src='../share/amber.gif' title='Unknown!' alt='Missing Image!'></td>
    </tr>
    </table>
    ";
    }
    ?>
    </div>
    
    <div class=inner>
    
    <table>
    <tr><th colspan=2>System</th></tr>
    <tr><td style='text-align:right'>Hostname</td>
    <td style='text-align:left'><?php echo $sys_host ?></td>
    </tr><tr><td style='text-align:right'>Time</td><td style='text-align:left'><?php echo $sys_time ?></td></tr>
    <tr><td style='text-align:right'>Uptime</td><td style='text-align:left'><span id=ut_text><?php echo $sys_up ?></span> seconds <button id=ut_refresh>Refresh</button> Auto :
    <select id=ut_select>
      <option value=0>Off</option>
      <option value=1>1s</option>
      <option value=5>5s</option>
    </select>
    </td></tr>
    </table>
    
    <table>
    <tr><th colspan=2>CPU</th></tr>
    <tr><td style='text-align:right'>Model</td>
    <td style='text-align:left'><?php echo $cpu_model ?></td></tr>
    <tr><td style='text-align:right'>Cores</td><td style='text-align:left'><?php echo $cpu_cores ?></td></tr>
    <tr><td style='text-align:right'>Load</td><td style='text-align:left'><?php echo $sys_load ?></td></tr>
    </table>
    
    <table>
    <tr><th colspan=2>Memory</th></tr>
    <tr><td style='text-align:right'>Total</td><td style='text-align:right'><?php echo $mem_total ?> Mb</td></tr>
    <tr><td style='text-align:right'>Used</td><td style='text-align:right'><?php echo $mem_used ?> Mb</td></tr>
    <tr><td style='text-align:right'>Free</td><td style='text-align:right'><?php echo $mem_free ?> Mb</td></tr>
    </table>
    
    <table>
    <tr><th colspan=2>Network</th></tr>
    <tr><td style='text-align:right'>MAC Address</td><td style='text-align:left'><?php echo $net_mac ?></td></tr>
    <tr><td style='text-align:right'>Internal IP</td><td style='text-align:left'><?php echo $net_ip_loc ?></td></tr>
    <tr><td style='text-align:right'>External IP</td><td style='text-align:left'><?php echo $net_ip_ext ?></td></tr>
    </table>
    
    </div>
    
    </div>
    
    <?php require '../share/footer.php'; ?>
    
    Direct download available here :-
    steve@Desktop:~/swuk_tutorial$ wget https://spacewire.co.uk/tutorial/shared/repos/0035/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/system/index.php.txt -O zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/system/index.php
    Check out the changes.
    steve@Desktop:~/swuk_tutorial$ git difftool zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/system/index.php

    6. Update System Javascript

    Update the System Javascript to include the following changes :-
    steve@Desktop:~/swuk_tutorial$ subl zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/system/script.js

    script.js

    //
    // File .......... script.js
    // Author ........ Steve Haywood
    // Website ....... http://www.spacewire.co.uk
    // Project ....... Zedboard Linux (SpaceWire UK Tutorial)
    // Date .......... 13 Mar 2026
    // Version ....... 9.0
    // History .......
    //   7.0 zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/uptime.js
    // Description ...
    //   Javascript for System Information & Firmware Load.
    //
    
    // Interval between PetaLinux uptime reads
    var timer_uptime;
    
    // Web elements
    const fw_select   = document.getElementById("fw_select");
    const fw_action   = document.getElementById("fw_action");
    const fw_progress = document.getElementById("fw_progress");
    const fw_status   = document.getElementById("fw_status");
    
    // Load PL Firmware
    function fw_reload() {
      if (fw_select.value != "none") {
        fw_select.disabled = true;
        fw_action.innerHTML = "Write";
        fw_progress.value = 0;
        fw_status.src = "../share/amber.gif?" + Date.now();
        fw_status.title = "Unknown!";
        var url = "/cgi-bin/loadfirmware?" + fw_select.value;
        if (window.XMLHttpRequest) {
          var ajaxReq = new XMLHttpRequest();
          ajaxReq.onreadystatechange = function() {
            if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
              var respText = ajaxReq.responseText;
              if (respText.substr(0,6) == "Error:") {
                fw_status.src = "../share/red.gif?" + Date.now();
                fw_status.title = "Last loadfirmware failed : " + respText.substr(7);
              } else {
                fw_status.src = "../share/green.gif?" + Date.now();
                fw_status.title = "Last loadfirmware successful";
              }
              fw_action.innerHTML = "Done";
              fw_progress.value = fw_progress.max;
              fw_select.disabled = false;
              fw_read_ids();
            }
          }
          ajaxReq.open("POST", url, true);
          ajaxReq.send(null);
        }
      }
    }
    
    
    // Download OS information file & display result
    async function os_read_ids() {
      let response = await fetch("/project.txt?" + Date.now());
      if (response.status == 200) {
        let ids = await response.text();
        fields = ids.split(/\r?\n/);
      }
      for (var i = 0; i < 6; i++) {
        const now = Date.now();
        const txt_obj = document.getElementById("os_id_" + i);
        const img_obj = document.getElementById("os_id_status_" + i);
        if (response.status == 200) {
          if (i < fields.length && fields[i] != "") {
            img_obj.src = "../share/green.gif?" + now;
            img_obj.title = "Last file fetch successful";
            txt_obj.innerHTML = fields[i];
          } else {
            img_obj.src = "../share/red.gif?" + now;
            img_obj.title = "Missing field information";
            txt_obj.innerHTML = "Unknown";
          }
        } else {
          img_obj.src = "../share/red.gif?" + now;
          img_obj.title = "Last file fetch failed";
          txt_obj.innerHTML = "Unknown";
        }
      }
    }
    
    // Peek all strings
    function fw_read_ids() {
      fw_read_id(c_id_description, 0);
      fw_read_id(c_id_company, 1);
      fw_read_id(c_id_author, 2);
      fw_read_id(c_id_version, 3);
      fw_read_id(c_id_timestamp, 4);
      fw_read_id(c_id_hash, 5);
    }
    
    // Peek string & display result
    function fw_read_id(offset, reg) {
      var url = "/cgi-bin/peekstring?" + c_axi_identification + "&" + 4096 + "&" + offset + "&" + 128;
      if (window.XMLHttpRequest) {
        var ajaxReq = new XMLHttpRequest();
        ajaxReq.onreadystatechange = function() {
          if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
            var respText = ajaxReq.responseText;
            var img_obj = document.getElementById("fw_id_status_" + reg);
            // Unique number is added to image to avoid caching issues on separate animations
            const now = Date.now();
            if (respText.substr(0,6) == "Error:") {
              img_obj.src = "../share/red.gif?" + now;
              img_obj.title = "Last peekstring failed : " + respText.substr(7);
            } else {
              const now = Date.now();
              img_obj.src = "../share/green.gif?" + now;
              img_obj.title = "Last peekstring successful";
              document.getElementById("fw_id_" + reg).innerHTML = respText;
            }
          }
        }
        ajaxReq.open("POST", url, true);
        ajaxReq.send(null);
      }
    }
    
    // Get uptime
    function get_uptime() {
      var url = "/cgi-bin/uptime.cgi";
      if (window.XMLHttpRequest) {
        var ajaxReq = new XMLHttpRequest();
        ajaxReq.onreadystatechange = function() {
          if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
            var respText = ajaxReq.responseText;
            var txtObj = document.getElementById("ut_text");
            if (txtObj) {
              txtObj.innerHTML = respText;
            }
          }
        }
        ajaxReq.open("POST", url, true);
        ajaxReq.send(null);
      }
    }
    
    // Update uptime timer
    function uptime() {
      clearInterval(timer_uptime);
      var uptime = document.getElementById("ut_select");
      if (uptime) {
        var interval = uptime.value;
        if (interval > 0) {
          timer_uptime = setInterval("get_uptime()", 1000 * interval);
        }
      }
    }
    
    
    //// Update BOOT files & reboot system /////////////////////////////////////////
    
    // Web elements
    const os_select   = document.getElementById("os_select");
    const os_action   = document.getElementById("os_action");
    const os_progress = document.getElementById("os_progress");
    const os_count    = document.getElementById("os_count");
    const os_status   = document.getElementById("os_status");
    
    // Global variables
    var os_timer;
    var os_ping;
    var os_terminal;
    var os_counter;
    
    // A very crude ping
    function ping() {
      const xhr = new XMLHttpRequest();
      xhr.timeout = 500;
      xhr.open("GET", "", true);
      xhr.onreadystatechange = function() {
        if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) {
          os_ping = 1;
        }
      }
      xhr.send();
    }
    
    // Handle sequence of events for OS reload
    function os_ticker() {
      os_progress.value++;
      os_counter++;
      os_count.innerHTML = os_counter + "/" + os_progress.max + "s";
      if (os_counter == 30) {
        os_action.innerHTML = "Ping";
      } else if (os_counter == os_progress.max) {
        clearInterval(os_timer);
        os_action.innerHTML = "Done";
        os_status.src = "../share/red.gif?" + Date.now();
        os_status.title = "Reload OS timed out, check connection!";
        os_select.disabled = false;
        fw_select.disabled = false;
      } else if (os_counter == os_terminal - 1) {
        os_action.innerHTML = "Reload";
      } else if (os_counter == os_terminal) {
        clearInterval(os_timer);
        window.location.reload(true);
      } else if (os_terminal == -1 && os_counter > 30) {
        if (os_ping == 1) {
          os_action.innerHTML = "Done";
          os_progress.value = os_progress.max;
          os_status.src = "../share/green.gif?" + Date.now();;
          os_status.title = "Reload OS successful";
          os_terminal = os_counter + 2;
        } else {
          ping();
        }
      }
    }
    
    // Kick off OS reload
    function os_reload(obj) {
      if (os_select.value != "none") {
        os_select.disabled = true;
        fw_select.disabled = true;
        os_action.innerHTML = "Install";
        os_progress.value = 0;
        os_count.innerHTML = "0/" + os_progress.max + "s";
        os_status.src = "../share/amber.gif?" + Date.now();
        os_status.title = "Unknown!";
        os_ping = 0;
        os_terminal = -1;
        os_counter = 0;
        os_timer = setInterval("os_ticker()", 1000);
        const url = "/cgi-bin/reload_os.cgi?" + os_select.value;
        if (window.XMLHttpRequest) {
          const ajaxReq = new XMLHttpRequest();
          ajaxReq.onreadystatechange = function() {
            if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
              const respText = ajaxReq.responseText;
              if (respText == "OK") {
                os_action.innerHTML = "Reboot";
              } else {
                clearInterval(os_timer);
                os_action.innerHTML = "Done";
                os_status.src = "../share/red.gif?" + Date.now();
                os_status.title = respText;
                os_select.disabled = false;
                fw_select.disabled = false;
              }
            }
          }
          ajaxReq.open("POST", url, true);
          ajaxReq.send(null);
        }
      }
    }
    
    // Page load events
    window.addEventListener("load", (event) => {
      os_read_ids();
      fw_read_ids();
    });
    
    // Operating system reload events
    document.getElementById("os_select").addEventListener("change", function() { os_reload(); });
    document.getElementById("os_read").addEventListener("click", function() { os_read_ids(); });
    
    // Firware reload events
    document.getElementById("fw_select").addEventListener("change", function() { fw_reload(); });
    document.getElementById("fw_read").addEventListener("click", function() { fw_read_ids(); });
    
    // Uptime events
    document.getElementById("ut_select").addEventListener("change", function() { uptime(); });
    document.getElementById("ut_refresh").addEventListener("click", function() { get_uptime(); });
    
    Direct download available here :-
    steve@Desktop:~/swuk_tutorial$ wget https://spacewire.co.uk/tutorial/shared/repos/0035/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/system/script.js -O zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/system/script.js
    Check out the changes.
    steve@Desktop:~/swuk_tutorial$ git difftool zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/system/script.js

    7. Update Peek & Poke webpage

    Update the Peek & Poke webpage to include the following changes :-
    steve@Desktop:~/swuk_tutorial$ subl zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/index.php

    index.php

    <?php
    //
    // File .......... index.php
    // Author ........ Steve Haywood
    // Website ....... http://www.spacewire.co.uk
    // Project ....... Zedboard Linux (SpaceWire UK Tutorial)
    // Date .......... 13 Mar 2026
    // Version ....... 11.0
    // History .......
    //   9.0 zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/index.php
    //   7.0 zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/index.cgi
    // Description ...
    //   Webpage for Peek & Poke Addresses.
    //
    ?>
    
    <?php require '../share/header.php'; ?>
    
    <div class="content">
    
    <table id="registers">
    <tr>
    <th>Address</th>
    <th>Hex</th>
    <th>Peek Value</th>
    <th>Sel</th>
    <th>Peek</th>
    <th>Status</th>
    <th>Copy</th>
    <th colspan="2">Poke Value</th>
    <th>Sel</th>
    <th>Poke</th>
    <th>Status</th>
    <th>Description</th>
    </tr>
    </table>
    <br><br>
    <input title="Add new row to end of address table" type="button" value="Add" onclick="add_row()">
    <select title="Set type of row to add to address table" id="type">
      <option value="0">Register</option>
      <option value="1">Section</option>
    </select>
    <input title="Remove last address from table" type="button" value="Remove" onclick="rem_register()">
    <input title="Peek all selected addresses in table" type="button" value="Peek All" onclick="peek_all()">
    <input title="Copy all table peek values into poke values" type="button" value="Copy All" onclick="copy_all()">
    <input title="Poke all selected addresses in table" type="button" value="Poke All" onclick="poke_all()">
    Peek Refresh :
    <select title="Set timer interval for automatic peek of table addresses" id="timer" onchange="timer()">
      <option value="0">Off</option>
      <option value="1">1s</option>
      <option value="5">5s</option>
    </select>
    Configuration :
    <select title='Read configuration file into table' id=config_select>
    <option value=none>N/A</option>
    <?php
    $location = '/media/sd-mmcblk0p1/configuration/*.txt';
    foreach(glob("$location") as $file) {
      if (is_file($file)) {
        $basename = pathinfo($file)["basename"];
        $filename = pathinfo($file)["filename"];
        echo "<option value=$basename>$filename</option>";
      }
    }
    ?>
    </select>
    <button title="Create configuration file from table" onclick="create_config()">Create...</button> <a title="Right click and Save Link As... to locate and rename this file" download="config.txt" id="download" href="" style="display: none">config.txt</a>
    <input title="Read configuration file into table" type="file" id="load_config">
    
    </div>
    
    <?php require '../share/footer.php'; ?>
    
    Direct download available here :-
    steve@Desktop:~/swuk_tutorial$ wget https://spacewire.co.uk/tutorial/shared/repos/0035/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/index.php.txt -O zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/index.php
    Check out the changes.
    steve@Desktop:~/swuk_tutorial$ git difftool zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/index.php

    8. Update Peek & Poke Javascript

    Update the Peek & Poke Javascript to include the following changes :-
    steve@Desktop:~/swuk_tutorial$ subl zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/script.js

    script.js

    //
    // File .......... script.js
    // Author ........ Steve Haywood
    // Website ....... http://www.spacewire.co.uk
    // Project ....... Zedboard Linux (SpaceWire UK Tutorial)
    // Date .......... 13 Mar 2026
    // Version ....... 9.0
    // History .......
    //   7.0 zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/uptime.js
    // Description ...
    //   Javascript for Peek & Poke Addresses.
    //
    
    // Constants
    const c_uns = 0; // Unsigned string
    const c_hex = 1; // Hexadecimal string
    
    // Requests
    var timer_peek_all;
    
    // Update peek_all timer
    function timer() {
      clearInterval(timer_peek_all);
      var timer = document.getElementById("timer");
      interval = timer.value;
      if (interval > 0) {
        timer_peek_all = setInterval("peek_all()", 1000 * interval);
      }
    }
    
    // Convert unsigned/hexadecimal number string into a specified number string.
    function fmtUnsignedLong(string, format) {
      var hexStr;
      const value = parseInt(string);
      switch(format) {
        case c_uns:
          hexStr = value.toString(10);
          break;
        case c_hex:
          hexStr = value.toString(16).toUpperCase();
          hexStr = "0x" + "00000000".substr(0, 8 - hexStr.length) + hexStr;
          break;
        default:
          break;
      }
      return hexStr;
    }
    
    // Call fmtUnsignedLong with type setting from row in peek/poke address table.
    function fmtUnsignedLongReg(reg, value) {
      const disp_obj = document.getElementById("display_" + reg);
      var hexStr = "0xNaN";
      if (disp_obj) {
        const format = (disp_obj.checked) ? (c_hex) : (c_uns);
        hexStr = fmtUnsignedLong(value, format);
      }
      return hexStr;
    }
    
    // Copy peek to poke & update any associated widgets
    function copy(reg) {
      const peek_obj = document.getElementById("peek_" + reg);
      if (peek_obj) {
        const poke_obj = document.getElementById("poke_" + reg);
        if (poke_obj) {
          poke_obj.value = peek_obj.value;
        }
        const range_obj = document.getElementById("poke_range_" + reg);
        if (range_obj) {
          range_obj.value = parseInt(peek_obj.value);
        }
        const select_obj = document.getElementById("poke_select_" + reg);
        if (select_obj) {
          select_obj.value = peek_obj.value;
        }
      }
    }
    
    // Copy selected peek to poke in section
    function copy_section(row) {
      var obj_sel;
      do {
        row++;
        obj_sel = document.getElementById("peek_sel_" + row);
        if (obj_sel) {
          if (obj_sel.checked) {
            copy(row);
          }
        }
      } while (obj_sel);
    }
    
    // Copy all peek to poke
    function copy_all() {
      var table = document.getElementById("registers");
      var rows = table.rows.length - 1;
      for (var index = 0; index < rows; index++) {
        copy(index);
      }
    }
    
    // Peek address & display result
    function peek(reg) {
      var url = "/cgi-bin/bitbash?" + "peek" + "&" + document.getElementById("addr_" + reg).value;
      if (window.XMLHttpRequest) {
        var ajaxReq = new XMLHttpRequest();
        ajaxReq.onreadystatechange = function() {
          if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
            var respText = ajaxReq.responseText;
            var img_obj = document.getElementById("speek_" + reg);
            // Unique number is added to image to avoid caching issues on separate animations
            const now = Date.now();
            if (respText.substr(0,6) == "Error:") {
              img_obj.src = "../share/red.gif?" + now;
              img_obj.title = "Last peek failed : " + respText.substr(7);
            } else {
              img_obj.src = "../share/green.gif?" + now;
              img_obj.title = "Last peek successful";
              document.getElementById("peek_" + reg).value = fmtUnsignedLongReg(reg, respText);
            }
          }
        }
        ajaxReq.open("POST", url, true);
        ajaxReq.send(null);
      }
    }
    
    // Peek all selected addresses in section
    function peek_section(row) {
      var obj_sel;
      do {
        row++;
        obj_sel = document.getElementById("peek_sel_" + row);
        if (obj_sel) {
          if (obj_sel.checked) {
            peek(row);
          }
        }
      } while (obj_sel);
    }
    
    // Peek all selected addresses in table
    function peek_all() {
      var table = document.getElementById("registers");
      var rows = table.rows.length - 1;
      for (var index = 0; index < rows; index++) {
        const obj_sel = document.getElementById("peek_sel_" + index);
        if (obj_sel) {
          if (obj_sel.checked) {
            peek(index);
          }
        }
      }
    }
    
    function poke(reg) {
      var url = "/cgi-bin/bitbash?" + "poke" + "&" + document.getElementById("addr_" + reg).value + "&" + document.getElementById("poke_" + reg).value;
      if (window.XMLHttpRequest) {
        var ajaxReq = new XMLHttpRequest();
        ajaxReq.onreadystatechange = function() {
          if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
            var respText = ajaxReq.responseText;
            var img_obj = document.getElementById("spoke_" + reg);
            // Unique number is added to image to avoid caching issues on separate animations
            const now = Date.now();
            if (respText.substr(0,6) == "Error:") {
              img_obj.src = "../share/red.gif?" + now;
              img_obj.title = "Last poke failed : " + respText.substr(7);
            } else {
              img_obj.src = "../share/green.gif?" + now;
              img_obj.title = "Last poke successful";
            }
          }
        }
        ajaxReq.open("POST", url, true);
        ajaxReq.send(null);
      }
    }
    
    // Poke value from input widget & update any associated widgets
    function poke_widget(reg) {
      const poke_obj = document.getElementById("poke_" + reg);
      if (poke_obj) {
        const range_obj = document.getElementById("poke_range_" + reg);
        if (range_obj) {
          range_obj.value = parseInt(poke_obj.value);
        }
        const select_obj = document.getElementById("poke_select_" + reg);
        if (select_obj) {
          select_obj.value = poke_obj.value;
        }
      }
      poke(reg);
    }
    
    // Poke value on Enter key from within input widget
    // Holding down Enter keeps on poking which is not desired - needs improvement!
    function poke_key(reg) {
      if (event.key === 'Enter') {
        poke_widget(reg);
      }
    }
    
    // Update radix for peek & poke input widgets
    function update_radix(reg) {
      const poke_obj = document.getElementById("poke_" + reg);
      if (poke_obj) {
        poke_obj.value = fmtUnsignedLongReg(reg, poke_obj.value);
      }
      const peek_obj = document.getElementById("peek_" + reg);
      if (peek_obj) {
        peek_obj.value = fmtUnsignedLongReg(reg, peek_obj.value);
      }
    }
    
    // Poke value from range widget & update any associated widgets
    function poke_range(reg) {
      const range_obj = document.getElementById("poke_range_" + reg);
      if (range_obj) {
        const poke_obj = document.getElementById("poke_" + reg);
        if (poke_obj) {
          poke_obj.value = fmtUnsignedLongReg(reg, range_obj.value);
        }
      }
      poke(reg);
    }
    
    // Poke value from select widget & update any associated widgets
    function poke_select(reg) {
      const select_obj = document.getElementById("poke_select_" + reg);
      if (select_obj) {
        const poke_obj = document.getElementById("poke_" + reg);
        if (poke_obj) {
          poke_obj.value = fmtUnsignedLongReg(reg, select_obj.value);
        }
      }
      poke(reg);
    }
    
    // Poke all selected addresses in section
    function poke_section(row) {
      var obj_sel;
      do {
        row++;
        obj_sel = document.getElementById("poke_sel_" + row);
        if (obj_sel) {
          if (obj_sel.checked) {
            poke_widget(row);
          }
        }
      } while (obj_sel);
    }
    
    // Poke all selected addresses in table
    function poke_all() {
      var table = document.getElementById("registers");
      var rows = table.rows.length - 1;
      for (var index = 0; index < rows; index++) {
        const obj_sel = document.getElementById("poke_sel_" + index);
        if (obj_sel) {
          if (obj_sel.checked) {
            poke_widget(index);
          }
        }
      }
    }
    
    // Add row to table
    function add_row() {
      const obj_type = document.getElementById("type");
      const row_type = obj_type.value;
      switch(row_type) {
        case "0":
          add_register();
          break;
        case "1":
          add_section();
          break;
        default:
          break;
      }
    }
    
    // Add peek/poke row to table
    function add_register(reg) {
      const table = document.getElementById("registers");
      const next = table.rows.length - 1;
      var addr = c_axi_gpio_zed;
      if (next > 0) {
        const obj_addr = document.getElementById("addr_" + (next - 1));
        if (obj_addr) {
          addr = parseInt(obj_addr.value) + 4;
        }
      }
      const fields = ["reg", addr.toString(), "true", "true", "0x00000000", "true", "Register @ " + fmtUnsignedLong(addr.toString(), c_hex)];
      add_row_raw(fields);
    }
    
    // Add section row to table
    function add_section(reg) {
      const fields = ["sec", "Section Description"];
      add_row_raw(fields);
    }
    
    // Add row to table
    function add_row_raw(fields) {
      const table = document.getElementById("registers");
      const next = table.rows.length - 1;
      const row = table.insertRow(-1);
      var newcell;
    
      // Separate out fields
      var type            = fields[0];
      if (type == "sec") {
        var description   = fields[1];
      } else {
        var address       = fields[1];
        var display_type  = fields[2];
        var peek_select   = fields[3];
        var options       = fields[4].split("#");
        var poke_select   = fields[5];
        var description   = fields[6];
        // Decode fields
        var disp_checked  = (display_type == "true") ? ("checked") : ("");
        var format        = (display_type == "true") ? (c_hex) : (c_uns);
        var peek_checked  = (peek_select == "true") ? ("checked") : ("");
        var poke_checked  = (poke_select == "true") ? ("checked") : ("");
      }
    
      newcell = row.insertCell(-1);
      if (type == "sec") {
        newcell.colSpan = "4";
        newcell.innerHTML = "--- Section ---";
      } else {
        newcell.innerHTML = '<input title="Address to peek/poke" type="text" id="addr_' + next + '" value="' + fmtUnsignedLong(address, c_hex) + '" size="10">';
    
        newcell = row.insertCell(-1);
        newcell.innerHTML = '<input title="Select peak/poke display type (unsigned/hexadecimal)" type="checkbox" id="display_' + next + '" ' + disp_checked + ' onchange="update_radix(' + next + ')">';
    
        newcell = row.insertCell(-1);
        newcell.innerHTML = '<input title="Value peeked at address" type="text" id="peek_' + next + '" value="'+ fmtUnsignedLong("0x0", format) +'" size="10" readonly="readonly">';
    
        newcell = row.insertCell(-1);
        newcell.innerHTML = '<input title="Select address for peeking" type="checkbox" id="peek_sel_' + next + '" ' + peek_checked + '>';
      }
    
      newcell = row.insertCell(-1);
      if (type == "sec") {
        newcell.innerHTML = '<input title="Peek all selected addresses in section" type="submit" value="Peek" onclick="peek_section(' + next + ')">';
      } else {
        newcell.innerHTML = '<input title="Peek address" type="submit" value="Peek" onclick="peek(' + next + ')">';
      }
    
      newcell = row.insertCell(-1);
      if (type == "sec") {
        newcell.innerHTML = '<img style="vertical-align:middle" src="../share/amber.gif" alt="Missing Image!">';
      } else {
        newcell.innerHTML = '<img title="Peek status" id="speek_' + next + '" style="vertical-align:middle" src="../share/amber.gif" alt="Missing Image!">';
      }
    
      newcell = row.insertCell(-1);
      if (type == "sec") {
        newcell.innerHTML = '<input title="Copy all selected peek values into poke values in section" type="submit" value=">>" onclick="copy_section(' + next + ')">';
      } else {
        newcell.innerHTML = '<input title="Copy peek value into poke value" type="submit" value=">>" onclick="copy(' + next + ')">';
      }
    
      newcell = row.insertCell(-1);
      switch (type) {
        case "sec":
          newcell.colSpan = "3";
          newcell.innerHTML = "--- Section ---";
          break;
        case "reg":
          const poke_value    = fields[4];
          newcell.innerHTML = '<input title="Value to poke at address" type="text" id="poke_' + next + '" value="'+ fmtUnsignedLong(poke_value, format) +'" size="10" onkeydown="poke_key(' + next + ')">';
          newcell = row.insertCell(-1);
          newcell.style.padding = "0";
          break;
        case "range":
          var range_value   = options[0];
          var range_min     = options[1];
          var range_max     = options[2];
          newcell.innerHTML = '<input title="Value to poke at address" type="text" id="poke_' + next + '" value="'+ fmtUnsignedLong(range_value, format) +'" size="10" onkeydown="poke_key(' + next + ')">';
          newcell = row.insertCell(-1);
          newcell.innerHTML = '<input title="Value to poke at address" style="width:100%" type="range" min="'+ range_min +'" max="'+ range_max +'" value="'+ range_value +'" id="poke_range_' + next + '" oninput="poke_range(' + next + ')">';
          break;
        case "select":
          var opt_str = "";
          var opt_sel;
          var opt_pair;
          const sel = options[0];
          for (var index = 1; index < options.length; index++) {
            opt_pair = options[index].split("^");
            opt_sel = (sel == index - 1) ? (" selected") : ("");
            opt_str = opt_str.concat('<option value="', opt_pair[0], '"', opt_sel + '>', opt_pair[1], '</option>');
          }
          newcell.innerHTML = '<input title="Value to poke at address" type="text" id="poke_' + next + '" value="'+ fmtUnsignedLong(sel, format) +'" size="10" onkeydown="poke_key(this, ' + next + ')">';
          newcell = row.insertCell(-1);
          newcell.innerHTML = '<select title="Value to poke at address" style="width:100%" id="poke_select_' + next + '" onchange="poke_select(' + next + ')">' + opt_str + '</select>';
          break;
        default:
          break;
      }
    
      if (type == "sec") {
        newcell = row.insertCell(-1);
        newcell.innerHTML = '<input title="Poke all selected addresses in section" type="submit" value="Poke" onclick="poke_section(' + next + ')">';
    
        newcell = row.insertCell(-1);
        newcell.innerHTML = '<img style="vertical-align:middle" src="../share/amber.gif" alt="Missing Image!">';
    
        newcell = row.insertCell(-1);
        newcell.innerHTML = '<input title="Description of section" type="text" id="name_' + next + '" value="' + description + '" size="40">';
      } else {
        newcell = row.insertCell(-1);
        newcell.innerHTML = '<input title="Select address for poking" type="checkbox" id="poke_sel_' + next + '" ' + poke_checked + '>';
    
        newcell = row.insertCell(-1);
        newcell.innerHTML = '<input title="Poke address" type="submit" value="Poke" onclick="poke_widget(' + next + ')">';
    
        newcell = row.insertCell(-1);
        newcell.innerHTML = '<img title="Poke status" id="spoke_' + next + '" style="vertical-align:middle" src="../share/amber.gif" alt="Missing Image!">';
    
        newcell = row.insertCell(-1);
        newcell.innerHTML = '<input title="Description of address" type="text" id="name_' + next + '" value="' + description + '" size="40">';
      }
    }
    
    // Remove row from table
    function rem_register(reg) {
      var table = document.getElementById("registers");
      if (table.rows.length > 1) {
        table.deleteRow(-1);
      }
    }
    
    // Remove all rows from table
    function remove_all() {
      var table = document.getElementById("registers");
      var rows = table.rows.length - 1;
      for (var index = 0; index < rows; index++) {
        table.deleteRow(-1);
      }
    }
    
    // Note: browser file access is made difficult for security reasons - there maybe a better way of doing file read & write.
    
    var config_file = null;
    
    // Create virtual configuration file from address table for user to download
    function create_config() {
      var text = "";
      var table = document.getElementById("registers");
      const rows = table.rows.length - 1;
      for (var index = 0; index < rows; index++) {
        const obj_addr = document.getElementById("addr_" + index);
        if (obj_addr) { // Register type
          var addr = document.getElementById("addr_" + index).value;
          var display = document.getElementById("display_" + index).checked;
          var peek_sel = document.getElementById("peek_sel_" + index).checked;
          var poke = document.getElementById("poke_" + index).value;
          var poke_sel = document.getElementById("poke_sel_" + index).checked;
          var name = document.getElementById("name_" + index).value;
          text += "reg" + "|" + addr + "|" + display + "|" + peek_sel + "|" + poke + "|" + poke_sel + "|"+ name + "\n";
        } else { // Section type
          var name = document.getElementById("name_" + index).value;
          text += "sec" + "|" + name + "\n";
        }
      }
      const data = new Blob([text], {type: 'text/plain'});
      if (config_file !== null) {
        URL.revokeObjectURL(config_file);
      }
      config_file = URL.createObjectURL(data);
      var link = document.getElementById('download');
      link.href = config_file;
      link.style.display = 'inline';
    }
    
    // Update address table
    function update_table(contents) {
      const lines = contents.split(/\r\n|\n/);
      remove_all();
      lines.forEach((line) => {
        if (line.length > 0) {
          var table = document.getElementById("registers");
          var next = table.rows.length - 1;
          const values = line.split("|");
          switch(values[0]) {
            case "reg":
            case "sec":
            case "range":
            case "select":
              add_row_raw(values);
              break;
            default:
              alert("Error: Unrecognized table type found (" + values[0] + "), ignoring.");
          }
        }
      });
    }
    
    // Read configuration file and update address table
    function load_config(input) {
      const file = input.target.files[0];
      if (file) {
        const reader = new FileReader();
        reader.onload = function(input) {
          const contents = input.target.result;
          update_table(contents);
        };
        reader.readAsText(file);
      }
    }
    
    // Web elements
    const config_select = document.getElementById("config_select");
    
    // Read server-side configuration file & handle via the client-side
    async function config_reload() {
      if (config_select.value != "none") {
        const response = await fetch("configuration/" + config_select.value + "?" + Date.now());
        if (response.status == 200) {
          const contents = await response.text();
          update_table(contents);
        }
      }
    }
    
    // Page load events
    window.addEventListener("load", (event) => { add_register(); });
    
    // Configuration events
    document.getElementById('load_config').addEventListener('change', load_config, false);
    document.getElementById("config_select").addEventListener("change", function() { config_reload(); });
    
    Direct download available here :-
    steve@Desktop:~/swuk_tutorial$ wget https://spacewire.co.uk/tutorial/shared/repos/0035/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/script.js -O zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/script.js
    Check out the changes.
    steve@Desktop:~/swuk_tutorial$ git difftool zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/script.js
    #### Part 4 - Check everything is working as expected (beta) ####

    9. Patch Zedboard PetaLinux v17.0

    Patch the currently running Zedboard PetaLinux v17.0 with the required files to add in the new Reload Operating System & updated Reload Firmware functionality.

    Sadly with this newer version of PetaLinux and its more restrictive access, we've been thrown out of our own party! No root ssh/scp, thus no access to /swv/www since root owns it. This is not a bad thing!

    Since access to the petalinux account is allowed, copy the files there first and then use sudo to move them.
    steve@Desktop:~/swuk_tutorial$ swuk_ssh 192.168.2.87
    steve@Desktop:~/swuk_tutorial$ sshpass -p petalinux scp -r zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website petalinux@192.168.2.87:/home/petalinux
    steve@Desktop:~/swuk_tutorial$ sshpass -p petalinux ssh -t petalinux@192.168.2.87 "sudo sh -c 'cp website/files/cgi-bin/reload_os.cgi /srv/www/cgi-bin/reload_os.cgi; cp website/files/share/header.php /srv/www/share/header.php; cp website/files/peekpoke/index.php /srv/www/peekpoke/index.php; cp website/files/peekpoke/script.js /srv/www/peekpoke/script.js; cp website/files/system/index.php /srv/www/system/index.php; cp website/files/system/script.js /srv/www/system/script.js'"

    10. Reload OS into Zedboard PetaLinux v16.0

    Access the webserver running on the Zedboard using a browser pointing at the Zedboard's IP address (192.168.2.87).

    Select System from the menu bar. Do a "Shift-Refresh" (Hard Reset) to clear the cache & reload the page (use Shift + Reload Button or Ctrl + Shift + R).

    The Operating System Information table should show Zedboard PetaLinux description along with a version of 17.0.

    The Firmware Information table should show Zedboard Video TPG HDMI description along with a version of 1.0.

    There should be no unsavoury comments after the version numbers!

    A new Reload Operating System drop-down should now be present.

    The Loadable Firmware table should now be gone and replaced with a Reload Firmware drop-down.

    Select v16.0 from the Reload Operating System drop-down in an attempt to change the Operating System in use from the current v17.0 back to the previous v16.0.

    After the reload sequence has finished...

    Welcome back to PetaLinux v16.0 along with its original Loadable Firmware table.



    Looks good!

    11. Patch Zedboard PetaLinux v16.0

    Patch the currently running Zedboard PetaLinux v16.0 with the required files to add in the new Reload Operating System & updated Reload Firmware functionality.

    This time around uploading the files can be done using the root account since ssh is enabled for root on Zedboard PetaLinux v16.0.
    steve@Desktop:~/swuk_tutorial$ swuk_ssh 192.168.2.87
    steve@Desktop:~/swuk_tutorial$ sshpass -p root scp zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/reload_os.cgi root@192.168.2.87:/srv/www/cgi-bin/reload_os.cgi
    steve@Desktop:~/swuk_tutorial$ sshpass -p root scp zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/header.php root@192.168.2.87:/srv/www/share/header.php
    steve@Desktop:~/swuk_tutorial$ sshpass -p root scp zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/index.php root@192.168.2.87:/srv/www/peekpoke/index.php
    steve@Desktop:~/swuk_tutorial$ sshpass -p root scp zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/script.js root@192.168.2.87:/srv/www/peekpoke/script.js
    steve@Desktop:~/swuk_tutorial$ sshpass -p root scp zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/system/index.php root@192.168.2.87:/srv/www/system/index.php
    steve@Desktop:~/swuk_tutorial$ sshpass -p root scp zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/system/script.js root@192.168.2.87:/srv/www/system/script.js

    12. Reload OS into Zedboard PetaLinux v17.0

    Access the webserver running on the Zedboard using a browser pointing at the Zedboard's IP address (192.168.2.87).

    Select System from the menu bar. Do a "Shift-Refresh" (Hard Reset) to clear the cache & reload the page (use Shift + Reload Button or Ctrl + Shift + R).

    The new Reload Operating System & Reload Firmware drop-down functionality should now be present again.

    Select v17.0 from the Reload Operating System drop-down in an attempt to change the Operating System in use from the current v16.0 back to the previous v17.0.

    After the reload sequence has finished...

    Welcome back to PetaLinux v17.0 along with its original Loadable Firmware table.



    Looks good! The Operating System can now be reloaded via the (server-side) Webserver using BOOT files held on the SD-Card or via the (client-side) Desktop swuk_upload_boot script using BOOT files held on the Desktop.

    13. Patch Zedboard PetaLinux v17.0

    Patch the currently running Zedboard PetaLinux v17.0 with the required files to add in the new Reload Operating System & updated Reload Firmware functionality.
    steve@Desktop:~/swuk_tutorial$ swuk_ssh 192.168.2.87
    steve@Desktop:~/swuk_tutorial$ sshpass -p petalinux scp -r zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website petalinux@192.168.2.87:/home/petalinux
    steve@Desktop:~/swuk_tutorial$ sshpass -p petalinux ssh -t petalinux@192.168.2.87 "sudo sh -c 'cp website/files/cgi-bin/reload_os.cgi /srv/www/cgi-bin/reload_os.cgi; cp website/files/share/header.php /srv/www/share/header.php; cp website/files/peekpoke/index.php /srv/www/peekpoke/index.php; cp website/files/peekpoke/script.js /srv/www/peekpoke/script.js; cp website/files/system/index.php /srv/www/system/index.php; cp website/files/system/script.js /srv/www/system/script.js'"

    14. Reload Firmware

    Access the webserver running on the Zedboard using a browser pointing at the Zedboard's IP address (192.168.2.87).

    Select System from the menu bar. Do a "Shift-Refresh" (Hard Reset) to clear the cache & reload the page (use Shift + Reload Button or Ctrl + Shift + R).

    The new Reload Operating System & Reload Firmware drop-down functionality should now be present again.

    Select zedboard_bram_id_v1.0 from the Reload Firmware drop-down in an attempt to change the firmware in use from Zedboard Video TPG HDMI to Zedboard Block RAM Identification.

    After the reload sequence has finished...

    Select zedboard_video_tpg_hdmi_v1.0 from the Reload Firmware drop-down in an attempt to change the firmware in use from Zedboard Block RAM Identification back to Zedboard Video TPG HDMI.



    Looks good! Some sage words required!

    15. Patch Zedboard PetaLinux v17.0

    Patch the currently running PetaLinux v17.0 such that it has a symbolic link from /srv/www/peekpoke/configuration (source) to /media/sd-mmcblk0p1/configuration (destination). This is to allow the Webserver to access files outside of its own user-space.
    steve@Desktop:~/swuk_tutorial$ swuk_ssh 192.168.2.87
    steve@Desktop:~/swuk_tutorial$ sshpass -p petalinux ssh -t petalinux@192.168.2.87 "sudo ln -s /media/sd-mmcblk0p1/configuration /srv/www/peekpoke/configuration"

    16. Load Peek & Poke Configuration

    Select Peek & Poke from the menu bar.

    Select zedboard_video_tpg_hdmi from the Configuration drop-down in an attempt to load the configuration file from the SD-Card.

    Click on Poke All to setup the Zedboard Video TPG HDMI registers to output video via HDMI.



    All being well the monitor should fire into life with the test pattern :-



    Click on the Browse... button and select zedboard_video_tpg_hdmi_control.txt from ~/Documents/swuk_totorial/configuration directory on the Linux Desktop.

    Click on Poke All to setup the Zedboard Video TPG HDMI registers to output video via HDMI.



    All being well the monitor should fire into life and display a Vertical Ramp background with both a Moving box & cross hairs overlay.

    All good! The Peek & Poke configurations can now be loaded via the (server-side) Webserver using files held on the SD-Card or via the (client-side) Webserver using files held on the Desktop.
    #### Part 5 - Make the changes permanent ####

    17. Bump version

    Change the version (or revision) number for this new development, this prevents ghost (post-release, same version) builds from appearing.
    steve@Desktop:~/swuk_tutorial$ sed -i 's/17.0/18.0/g' zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/project.txt
    Just for completeness :-

    project.txt

    Zedboard PetaLinux
    SpaceWire UK
    Steve Haywood
    18.0
    
    Direct download available here :-
    steve@Desktop:~/swuk_tutorial$ wget https://spacewire.co.uk/tutorial/shared/repos/0035/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/project.txt -O zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/project.txt
    Check out the changes.
    steve@Desktop:~/swuk_tutorial$ git difftool zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/project.txt

    18. Update Website BitBake recipe

    Update the website Bitbake recipe to include the following changes :-
    steve@Desktop:~/swuk_tutorial$ subl zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/website.bb

    website.bb

    #
    # File .......... website.bb
    # Author ........ Steve Haywood
    # Website ....... http://www.spacewire.co.uk
    # Project ....... Zedboard Linux (SpaceWire UK Tutorial)
    # Date .......... 13 Mar 2026
    # Version ....... 10.0
    # Description ...
    #   Application recipe for a dynamic (server & client side) website that is split
    # into separate sections (directories). Each section contains an index page
    # along with any associated files (sub-pages, scripts, styles, images, etc.).
    # There are also two shared areas for common files.
    #
    
    SUMMARY = "Simple website application"
    SECTION = "PETALINUX/apps"
    LICENSE = "MIT"
    LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
    
    SRC_URI = "file://project.txt"
    SRC_URI += "file://index.php"
    
    SRC_URI += "file://share/header.php"
    SRC_URI += "file://share/footer.php"
    SRC_URI += "file://share/style.css"
    SRC_URI += "file://share/script.js"
    SRC_URI += "file://share/amber.gif"
    SRC_URI += "file://share/green.gif"
    SRC_URI += "file://share/red.gif"
    
    SRC_URI += "file://cgi-bin/reload_os.cgi"
    SRC_URI += "file://cgi-bin/uptime.cgi"
    SRC_URI += "file://cgi-bin/test-cgi"
    SRC_URI += "file://cgi-bin/hello_world.php"
    SRC_URI += "file://cgi-bin/sqlite_test.php"
    SRC_URI += "file://cgi-bin/phpliteadmin.php"
    SRC_URI += "file://cgi-bin/phpliteadmin.config.php"
    
    SRC_URI += "file://home/index.php"
    
    SRC_URI += "file://system/index.php"
    SRC_URI += "file://system/script.js"
    
    SRC_URI += "file://peekpoke/index.php"
    SRC_URI += "file://peekpoke/script.js"
    
    SRC_URI += "file://zedboard/index.php"
    SRC_URI += "file://zedboard/script.js"
    SRC_URI += "file://zedboard/style.css"
    SRC_URI += "file://zedboard/zedboard.png"
    SRC_URI += "file://zedboard/sw_on.png"
    SRC_URI += "file://zedboard/sw_off.png"
    SRC_URI += "file://zedboard/sw_none.png"
    SRC_URI += "file://zedboard/btn_on.png"
    SRC_URI += "file://zedboard/btn_off.png"
    SRC_URI += "file://zedboard/btn_none.png"
    SRC_URI += "file://zedboard/led_on.png"
    SRC_URI += "file://zedboard/led_off.png"
    
    FILES:${PN} += "/srv/www"
    
    S = "${WORKDIR}"
    
    do_install() {
      install -d ${D}/srv/www
      install -m 0644 ${S}/project.txt ${D}/srv/www
      install -m 0644 ${S}/index.php ${D}/srv/www
    
      install -d ${D}/srv/www/share
      install -m 0644 ${S}/share/header.php ${D}/srv/www/share
      install -m 0644 ${S}/share/footer.php ${D}/srv/www/share
      install -m 0644 ${S}/share/style.css ${D}/srv/www/share
      install -m 0644 ${S}/share/script.js ${D}/srv/www/share
      install -m 0644 ${S}/share/amber.gif ${D}/srv/www/share
      install -m 0644 ${S}/share/green.gif ${D}/srv/www/share
      install -m 0644 ${S}/share/red.gif ${D}/srv/www/share
    
      install -d ${D}/srv/www/cgi-bin
      install -m 0777 -d ${D}/srv/www/cgi-bin/db
      install -m 0755 ${S}/cgi-bin/reload_os.cgi ${D}/srv/www/cgi-bin
      install -m 0755 ${S}/cgi-bin/uptime.cgi ${D}/srv/www/cgi-bin
      install -m 0755 ${S}/cgi-bin/test-cgi ${D}/srv/www/cgi-bin
      install -m 0644 ${S}/cgi-bin/hello_world.php ${D}/srv/www/cgi-bin
      install -m 0644 ${S}/cgi-bin/sqlite_test.php ${D}/srv/www/cgi-bin
      install -m 0644 ${S}/cgi-bin/phpliteadmin.php ${D}/srv/www/cgi-bin
      install -m 0644 ${S}/cgi-bin/phpliteadmin.config.php ${D}/srv/www/cgi-bin
    
      install -d ${D}/srv/www/home
      install -m 0644 ${S}/home/index.php ${D}/srv/www/home
    
      install -d ${D}/srv/www/system
      install -m 0644 ${S}/system/index.php ${D}/srv/www/system
      install -m 0644 ${S}/system/script.js ${D}/srv/www/system
    
      install -d ${D}/srv/www/peekpoke
      install -m 0644 ${S}/peekpoke/index.php ${D}/srv/www/peekpoke
      install -m 0644 ${S}/peekpoke/script.js ${D}/srv/www/peekpoke
    
      install -d ${D}/srv/www/zedboard
      install -m 0644 ${S}/zedboard/index.php ${D}/srv/www/zedboard
      install -m 0644 ${S}/zedboard/style.css ${D}/srv/www/zedboard
      install -m 0644 ${S}/zedboard/script.js ${D}/srv/www/zedboard
      install -m 0644 ${S}/zedboard/zedboard.png ${D}/srv/www/zedboard
      install -m 0644 ${S}/zedboard/sw_on.png ${D}/srv/www/zedboard
      install -m 0644 ${S}/zedboard/sw_off.png ${D}/srv/www/zedboard
      install -m 0644 ${S}/zedboard/sw_none.png ${D}/srv/www/zedboard
      install -m 0644 ${S}/zedboard/btn_on.png ${D}/srv/www/zedboard
      install -m 0644 ${S}/zedboard/btn_off.png ${D}/srv/www/zedboard
      install -m 0644 ${S}/zedboard/btn_none.png ${D}/srv/www/zedboard
      install -m 0644 ${S}/zedboard/led_on.png ${D}/srv/www/zedboard
      install -m 0644 ${S}/zedboard/led_off.png ${D}/srv/www/zedboard
    }
    
    Direct download available here :-
    steve@Desktop:~/swuk_tutorial$ wget https://spacewire.co.uk/tutorial/shared/repos/0035/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/website.bb -O zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/website.bb
    Check out the changes.
    steve@Desktop:~/swuk_tutorial$ git difftool zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/website.bb

    19. Update the auto-start application

    The configuration directory held on the SD-Card is outside of Apache's user-space (making it inaccessible). Edit the boot-extras application to include a link creation command that makes the configuration directory accessible for the Webserver Javascript.

    Also bump the Version & Date.
    steve@Desktop:~/swuk_tutorial$ subl zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/boot-extras/files/boot-extras

    boot-extras

    #!/bin/sh
    
    #
    # File .......... boot-extras
    # Author ........ Steve Haywood
    # Website ....... http://www.spacewire.co.uk
    # Project ....... Zedboard Linux (SpaceWire UK Tutorial)
    # Date .......... 13 Mar 2026
    # Version ....... 3.0
    # Description ...
    #   Script to perform boot time operations. Added enhancement to the link
    # command.
    #
    
    ln -s /run/media/*mmcblk0p1 /media/sd-mmcblk0p1
    ln -s /media/sd-mmcblk0p1/configuration /srv/www/peekpoke/configuration
    
    Direct download available here :-
    steve@Desktop:~/swuk_tutorial$ wget https://spacewire.co.uk/tutorial/shared/repos/0035/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/boot-extras/files/boot-extras -O zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/boot-extras/files/boot-extras
    Check out the changes.
    steve@Desktop:~/swuk_tutorial$ git difftool zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/boot-extras/files/boot-extras
    #### Part 6 - Revision Control ####

    20. Commit new & updated files

    Check GIT status to make sure all is well and there are no spurious elements.
    steve@Desktop:~/swuk_tutorial$ git status -u
    On branch master
    Your branch is up-to-date with 'origin/master'.

    Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git restore <file>..." to discard changes in working directory)
       modified:   zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/boot-extras/files/boot-extras
       modified:   zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/index.php
       modified:   zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/script.js
       modified:   zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/project.txt
       modified:   zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/header.php
       modified:   zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/system/index.php
       modified:   zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/system/script.js
       modified:   zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/website.bb

    Untracked files:
      (use "git add <file>..." to include in what will be committed)
       zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/reload_os.cgi

    no changes added to commit (use "git add" and/or "git commit -a")
    Looks good!

    Commit the updates, create an annotated tag and push the commit & tag up to the remote repository.
    steve@Desktop:~/swuk_tutorial$ git add -A
    steve@Desktop:~/swuk_tutorial$ git commit -a -m "Added Reload Operating System function. Updated Reload Firmware function. Added Peek & Poke local configuration file load. This version of PetaLinux includes the XSA from zedboard_video_tpg_hdmi v1.0."
    steve@Desktop:~/swuk_tutorial$ git push
    steve@Desktop:~/swuk_tutorial$ git tag -a my_zedboard_linux_v18.0 -m "PetaLinux, Peek/Poke, LED Runner, LAMP (Apache, SQLite, PHP & myLiteAdmin), Peek/Poke CGI, Load Firmware CGI, PL Access, Style Sheet, Register Bank, ID Strings, ADV5711 configuration & Reload OS with XSA from zedboard_video_tpg_hdmi v1.0"
    steve@Desktop:~/swuk_tutorial$ git push origin my_zedboard_linux_v18.0

    21. Create production release

    Create a potential production release for the PetaLinux (v18.0) project using pure repository source.

    (optional) Clear out all the superfluous files from the project area (non-tracked files).
    steve@Desktop:~/swuk_tutorial$ cd zedboard_linux/os/petalinux
    steve@Desktop:~/swuk_tutorial/zedboard_linux/os/petalinux$ git clean -fdx
    (optional) Clear out the transfer area.
    steve@Desktop:~/swuk_tutorial/zedboard_linux/os/petalinux$ rm -rf /tftpboot/*
    Double check GIT status.
    steve@Desktop:~/swuk_tutorial/zedboard_linux/os/petalinux$ git status -u
    On branch master
    Your branch is up-to-date with 'origin/master'.

    nothing to commit, working tree clean
    Build/rebuild PetaLinux.
    steve@Desktop:~/swuk_tutorial/zedboard_linux/os/petalinux$ swuk_petalinux-build
    Note: A fresh build of PetaLinux takes a long time to complete and can be prone to failure due to internet downloads. If the build process fails, firstly check the GIT status & difftool for any noddy file changes, restore these file changes and then execute the build command again until it finally completes. Example below :-
    ::
    ::
    ERROR: Failed to build project. Check the ~/swuk_tutorial/zedboard_linux/os/petalinux/build/build.log file for more details...
    steve@Desktop:~/swuk_tutorial/zedboard_linux/os/petalinux$ git status -u
    On branch my_master
    Your branch is up-to-date with 'origin/my_master'.

    Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git restore <file>..." to discard changes in working directory)
       modified:   .petalinux/metadata
       modified:   project-spec/configs/config

    no changes added to commit (use "git add" and/or "git commit -a")
    steve@Desktop:~/swuk_tutorial/zedboard_linux/os/petalinux$ git difftool
    steve@Desktop:~/swuk_tutorial/zedboard_linux/os/petalinux$ git restore .petalinux/metadata project-spec/configs/config
    steve@Desktop:~/swuk_tutorial/zedboard_linux/os/petalinux$ swuk_petalinux-build
    ::
    ::
    INFO: Successfully copied built images to tftp dir: /tftpboot
    [INFO] Successfully built project
    Package PetaLinux to produce the boot image BOOT.BIN, this will include the first stage boot loader zynq_fsbl.elf, the programmable logic system.bit, the Linux boot loader u-boot.elf and the device tree blob system.dtb.
    steve@Desktop:~/swuk_tutorial/zedboard_linux/os/petalinux$ petalinux-package --boot --force --fsbl images/linux/zynq_fsbl.elf --fpga images/linux/system.bit --uboot images/linux/u-boot.elf
    Archive the generated files for safe keeping. These files are not held in the repository as they can be recreated from source.
    steve@Desktop:~/swuk_tutorial/zedboard_linux/os/petalinux$ mkdir -p ~/Documents/swuk_tutorial/petalinux/v18.0
    steve@Desktop:~/swuk_tutorial/zedboard_linux/os/petalinux$ cp /tftpboot/{BOOT.BIN,boot.scr,image.ub} ~/Documents/swuk_tutorial/petalinux/v18.0
    #### Part 7 - Check everything is working as expected (alpha) ####

    22. Copy BOOT files to the SD-Card

    Upload the newly created PetaLinux Boot & OS files to the SD-Card.
    steve@Desktop:~/swuk_tutorial/zedboard_linux/os/petalinux$ swuk_ssh 192.168.2.87
    steve@Desktop:~/swuk_tutorial/zedboard_linux/os/petalinux$ sshpass -p petalinux scp -r ~/Documents/swuk_tutorial/petalinux/v18.0 petalinux@192.168.2.87:/media/sd-mmcblk0p1/petalinux

    23. Patch Zedboard PetaLinux v17.0

    Patch the currently running Zedboard PetaLinux v17.0 with the required files to add in the new Reload Operating System & updated Reload Firmware functionality.

    Sadly with this newer version of PetaLinux and its more restrictive access, we've been thrown out of our own party! No root ssh/scp, thus no access to /swv/www since root owns it. This is not a bad thing!

    Since access to the petalinux account is allowed, copy the files there first and then use sudo to move them.
    steve@Desktop:~/swuk_tutorial$ swuk_ssh 192.168.2.87
    steve@Desktop:~/swuk_tutorial$ sshpass -p petalinux scp -r zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website petalinux@192.168.2.87:/home/petalinux
    steve@Desktop:~/swuk_tutorial$ sshpass -p petalinux ssh -t petalinux@192.168.2.87 "sudo sh -c 'cp website/files/cgi-bin/reload_os.cgi /srv/www/cgi-bin/reload_os.cgi; cp website/files/share/header.php /srv/www/share/header.php; cp website/files/peekpoke/index.php /srv/www/peekpoke/index.php; cp website/files/peekpoke/script.js /srv/www/peekpoke/script.js; cp website/files/system/index.php /srv/www/system/index.php; cp website/files/system/script.js /srv/www/system/script.js'"

    24. Reload the Operating System using the Webserver

    Refresh the System page. Do a "Shift-Refresh" (Hard Reset) to clear the cache & reload the page (use Shift + Reload Button or Ctrl + Shift + R).

    Select v18.0 from the drop-down menu in the Reload Operating System section. Missing Image! After the reload sequence has finished...

    The Operating System Information table should show Zedboard PetaLinux description along with a version of 18.0.

    The Firmware Information table should show Zedboard Video TPG HDMI description along with a version of 1.0.

    There should be no unsavoury comments after the version numbers! Missing Image! The Timestamp & Hash shown from GIT (1st entry) should marry up perfectly with the displayed Operating System Information.
    steve@Desktop:~/swuk_tutorial/zedboard_linux/os/petalinux$ git log -1
    commit df5cd3f8437bb145558a3ec123570ec8dc500396 (HEAD -> my_master, tag: my_zedboard_linux_v18.0, origin/my_master)
    Author: Steve Haywood <steve@spacewire.co.uk>
    Date:   Sun Mar 15 09:24:23 2026 +0000

        Added Reload Operating System function. Updated Reload Firmware function. Added Peek & Poke local configuration file load. This version of PetaLinux includes the XSA from zedboard_video_tpg_hdmi v1.0.
    Refresh the System page again to check that the Reload Operating System & Reload Firmware sections are permanent. Do a "Shift-Refresh" (Hard Reset) to clear the cache & reload the page (use Shift + Reload Button or Ctrl + Shift + R).

    Looks good!

    25. Quick functionally check

    Reload Firmware

    Select one of the later firmwares from the Reload Firmware drop-down in an attempt to change the firmware in use from Zedboard Video TPG HDMI to something else.

    After the reload sequence has finished...

    Observe that the Firmware Information table has been updated accordingly.

    Reload Operating System

    Since Zedboard PetaLinux v18.0 is the only version that contains the new Reload Operating System functionality (without a patch) the best quick check is to simply reload itself.

    Select v18.0 from the drop-down menu in the Reload Operating System section.

    After the reload sequence has finished...

    Observe that the Firmware Information table has reverted back to Zedboard Video TPG HDMI.

    Peek & Poke Configuration

    Select Peek & Poke from the menu bar.

    Select any of the configurations from the Configuration drop-down in an attempt to load the configuration file from the SD-Card.

    Click on the Browse... button and select a different configuration from the ~/Documents/swuk_totorial/configuration directory on the Linux Desktop.

    Observe that the Address table is updated in both cases.

    All good!
    #### Part 8 - Final resulting build file(s) ####

    26. Resulting BOOT files

    The resulting BOOT files from the above build can be obtained from here: v18.0 (2023.2).