Bar
SpaceWire UK
Specialist providers of VHDL Intellectual Property & Design Services
BarBarBarBar
Tutorial
Missing Image!
Part 19 - Add on-the-fly PL firmware load capability & enhance the peek/poke address table (22 February 2024)

Introduction

This tutorial details the steps required to load a new firmware image stored on the SD Card without the need to reboot PetaLinux. The Peek/Poke address table will also be enhanced to include a few new features (range widget, select widget, individual hex/dec display & copy section). Migration from JTAG load to SD Card load will be adapted from here on in so the SD Card can be used to provide extra files (firmware images in this case) to PetaLinux. The tutorial covers quite a range of activities and includes the use of numerous languages; C, HTML & Javascript.

Aims

The aims of this tutorial are as follows :-

    Part 1 - Project Setup

    1. Setup environment
    2. Obtain tutorial files from Bitbucket (optional)

    Part 2 - OS Development

    1. Change present working directory
    2. Bump Version
    3. Fix issue with Apache .bbappend
    4. Package project to produce boot image
    5. Copy files to SD Card
    6. Deploy on Zedboard via SD Card
    7. Launch terminal emulator
    8. Create load firmware C application
    9. Cross compile application
    10. Copy application to PetaLinux
    11. Create loadable binary for bitstream
    12. Check application is working as expected
    13. Modify Makefile & BitBake recipe

    Part 3 - Enhance website application

    1. Update HTML
    2. Update Javascript
    3. Check everything is working as expected

    Part 4 - Revisions Control

    1. Commit new & updated files
    2. Final checks
    #### Part 1 - Project Setup ####

    1. Setup environment

    Setup Xilinx design environment for the 2021.2 toolset.
    steve@Desktop:~$ xilinx
    Xilinx tools available tools at /opt/Xilinx :-
    1) 2021.2 - Vivado - SDK - Vitis - PetaLinux
    0) Exit
    Please select tools required or exit : 1

    Tools are as follows :-
    vivado @ /opt/Xilinx/Vivado/2021.2/bin/vivado
    vitis @ /opt/Xilinx/Vitis/2021.2/bin/vitis
    petalinux-build @ /opt/Xilinx/PetaLinux/2021.2/tool/tools/common/petalinux/bin/petalinux-build

    2. Obtain tutorial files from Bitbucket (optional)

    Starting from this point, having not followed the previous tutorials, can be achieved by obtaining the required files from BitBucket. The only prerequisite is that Part 1 - Installation of tools, setup of environment and creation of project area as been completed.

    Obtain OS source.
    steve@Desktop:~$ cd ~/projects
    steve@Desktop:~/projects$ git clone -b v12.0 https://bitbucket.org/spacewire_firmware/zedboard_linux
    #### Part 2 - OS Development ####

    3. Change present working directory

    Change the present working directory to be the project directory.
    steve@Desktop:~$ cd ~/projects/zedboard_linux/os/petalinux

    4. Bump Version

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

    5. Fix issue with Apache .bbappend

    The current location of the apache2_%.bbappend breaks the PetaLinux build process given a fresh clone of the repo (v12.0 tag). Moving this file to a more suitable location fixes the issue.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ mkdir -p project-spec/meta-user/recipes-httpd/apache2
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ git mv components/yocto/layers/meta-openembedded/meta-webserver/recipes-httpd/apache2/apache2_%.bbappend project-spec/meta-user/recipes-httpd/apache2/apache2_%.bbappend
    If step 2 was followed then execute the following to remove the stay directory & also to build PetaLinux.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ rm -r components
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ petalinux-build

    6. Package project to produce boot image

    Package the project 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:~/projects/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

    7. Copy files to SD Card

    Mount a FAT32 formatted SD card on the Xubuntu desktop and copy the required files onto it, then unmount the card so that it is ready for use on the Zedboard.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ cp /tftpboot/{BOOT.BIN,boot.scr,image.ub} /media/steve/petalinux

    8. Deploy on Zedboard via SD Card

    Power off the Zedboard, set the boot mode jumpers for SD card boot, insert the SD card and then power on the board. Missing Image!

    9. Launch terminal emulator

    If not already running, open up a new terminal and launch the MiniCom terminal emulator.
    steve@Desktop:~$ minized

    Welcome to minicom 2.7.1

    OPTIONS: I18n
    Compiled on Dec 23 2019, 02:06:26.
    Port /dev/ttyACM0, 06:34:25

    Press CTRL-A Z for help on special keys

    10. Create load firmware C application

    Create a new C application to access the FPGA Manager device driver and inform it to load a new bitstream. The FPGA Manager looks for bitstream images in the /lib/firmware directory, however it would be advantageous to load these from the SD Card. This can be achieved by simply creating a symbolic link, done within the C application.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ subl project-spec/meta-user/recipes-apps/peekpokecgi/files/loadfirmware.c
    Something similar to the following should do the trick.

    loadfirmware.c

    1. //
    2. // File .......... loadfirmware.c
    3. // Author ........ Steve Haywood
    4. // Website ....... http://www.spacewire.co.uk
    5. // Project ....... SpaceWire UK Tutorial
    6. // Version ....... 1.0
    7. // Conception .... 9 February 2024
    8. // Standard ...... C17 (ISO/IEC 9899:2018)
    9. // Description ...
    10. //   Very simple CGI to load PL bitstream.
    11. //

    12. #include <stdio.h>
    13. #include <stdlib.h>
    14. #include <unistd.h>
    15. #include <fcntl.h>
    16. #include <string.h>

    17. int main()
    18. {
    19.   char *query;
    20.   char *firmware = "/sys/class/fpga_manager/fpga0/firmware";
    21.   char *target = "/lib/firmware";
    22.   int   fd;

    23.   int sl = symlink("/media/sd-mmcblk0p1/firmware", "/lib/firmware");

    24.   printf("Content-Type: text/plain\n\n");

    25.   query = getenv("QUERY_STRING");
    26.   if (query) {
    27.     fd = open(firmware, O_WRONLY);
    28.     if (fd >= 0) {
    29.       write(fd, query, strlen(query));
    30.       close(fd);
    31.     } else printf("Error: Failed to open %s", firmware);
    32.   } else printf("Error: No QUERY_STRING");
    33. }

    11. Cross compile application

    Cross compile the C code targetting the ARM processor.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ arm-linux-gnueabihf-gcc project-spec/meta-user/recipes-apps/peekpokecgi/files/loadfirmware.c -o /tmp/loadfirmware

    12. Copy application to PetaLinux

    Copy the compiled application over to PetaLinux.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ ssh-keygen -f /home/steve/.ssh/known_hosts -R 192.168.2.87
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ ssh-keyscan -H 192.168.2.87 >> ~/.ssh/known_hosts
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ sshpass -p root scp /tmp/loadfirmware root@192.168.2.87:/srv/www/cgi-bin

    13. Create loadable binary for bitstream

    Create a bitstream only binary (.bin) to include the zedboard_leds_switches bitstream (.bit). To achieve this a Boot Image File (.bif) is required.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ cd /tmp
    steve@Desktop:/tmp$ printf 'all:\n{\n  system_wrapper.bit\n} ' > system_wrapper.bif

    system_wrapper.bif

    1. all:
    2. {
    3.   system_wrapper.bit
    4. }

    Copy the bitstream and generate the binary.
    steve@Desktop:/tmp$ cp ~/projects/zedboard_leds_switches/fw/vivado/project.runs/impl_1/system_wrapper.bit .
    steve@Desktop:/tmp$ bootgen -arch zynq -image system_wrapper.bif -process_bitstream bin -w
    steve@Desktop:/tmp$ cd -
    Create a firmware directory on the SD Card to hold the loadable firmware images and copy over the newly created binary
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ sshpass -p root ssh -t root@192.168.2.87 'mkdir /media/sd-mmcblk0p1/firmware'
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ sshpass -p root scp /tmp/system_wrapper.bit.bin root@192.168.2.87:/media/sd-mmcblk0p1/firmware/zedboard_leds_switches.bin

    14. Check application is working as expected

    Change the execute permissions on the loadfirmware application so it executes with root privileges (not recommended).
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ sshpass -p root ssh -t root@192.168.2.87 'chmod +s /srv/www/cgi-bin/loadfirmware'
    Using curl enter the following URL to command the poke CGI to write 0x18 to the LED register at 0x40010000. This should light up the two middle LEDs.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ curl -w "\nReceived %{size_download} bytes\n" "http://192.168.2.87/cgi-bin/poke?0x40010000&0x18"
    Using curl enter the following URL to command the loadfirmware CGI to load the firmware.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ curl -w "\nReceived %{size_download} bytes\n" "http://192.168.2.87/cgi-bin/loadfirmware?zedboard_leds_switches.bin"
    All being well the following message should be seen in the MiniCom terminal window and the two LEDs should be turned off (registers inside PL reset to firmware defaults).
    root@petalinux:~# fpga_manager fpga0: writing zedboard_leds_switches.bin to Xilinx Zynq FPGA Manager

    15. Modify Makefile & BitBake recipe

    With confidence that the load firmware application is working OK the Makefile & BitBake recipe can be modified to include the new application in the PetaLinux build process.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ subl project-spec/meta-user/recipes-apps/peekpokecgi/files/Makefile

    Makefile

    1. PEEK = peek
    2. POKE = poke
    3. PEEKSTRING = peekstring
    4. LOADFIRMWARE = loadfirmware

    5. # Add any other object files to this list below
    6. PEEK_OBJS = peek.o
    7. POKE_OBJS = poke.o
    8. PEEKSTRING_OBJS = peekstring.o
    9. LOADFIRMWARE_OBJS = loadfirmware.o

    10. all: $(PEEK) $(POKE) $(PEEKSTRING) $(LOADFIRMWARE)

    11. $(LOADFIRMWARE): $(LOADFIRMWARE_OBJS)
    12.   $(CC) $(LDFLAGS) -o $@ $(LOADFIRMWARE_OBJS) $(LDLIBS)

    13. $(PEEKSTRING): $(PEEKSTRING_OBJS)
    14.   $(CC) $(LDFLAGS) -o $@ $(PEEKSTRING_OBJS) $(LDLIBS)

    15. $(POKE): $(POKE_OBJS)
    16.   $(CC) $(LDFLAGS) -o $@ $(POKE_OBJS) $(LDLIBS)

    17. $(PEEK): $(PEEK_OBJS)
    18.   $(CC) $(LDFLAGS) -o $@ $(PEEK_OBJS) $(LDLIBS)

    19. clean:
    20.   -rm -f $(LOADFIRMWARE) $(PEEKSTRING) $(POKE) $(PEEK) *.elf *.gdb *.o

    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ subl project-spec/meta-user/recipes-apps/peekpokecgi/peekpokecgi.bb

    peekpokecgi.bb

    1. #
    2. # This is the peekpokecgi aplication recipe
    3. #
    4. #

    5. SUMMARY = "peekpokecgi application"
    6. SECTION = "PETALINUX/apps"
    7. LICENSE = "MIT"
    8. LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
    9. SRC_URI = "file://peek.c \
    10.            file://poke.c \
    11.            file://peekstring.c \
    12.            file://loadfirmware.c \
    13.            file://Makefile \
    14.           "
    15. FILES_${PN} += "/srv/www/cgi-bin"
    16. S = "${WORKDIR}"
    17. CFLAGS_prepend = "-I ${S}/include"
    18. do_compile() {
    19.         oe_runmake
    20. }
    21. do_install() {
    22.         install -d ${D}/srv/www/cgi-bin
    23.         install -m 0755 ${S}/peek ${D}/srv/www/cgi-bin
    24.         install -m 0755 ${S}/poke ${D}/srv/www/cgi-bin
    25.         install -m 0755 ${S}/peekstring ${D}/srv/www/cgi-bin
    26.         install -m 0755 ${S}/loadfirmware ${D}/srv/www/cgi-bin
    27.         chmod a+s ${D}/srv/www/cgi-bin/peek
    28.         chmod a+s ${D}/srv/www/cgi-bin/poke
    29.         chmod a+s ${D}/srv/www/cgi-bin/peekstring
    30.         chmod a+s ${D}/srv/www/cgi-bin/loadfirmware
    31. }
    #### Part 3 - Enhance website application ####

    16. Update HTML

    Update the webpage to remove the global Number Format option and replace it with an individual option for each row of the peek/poke address table. Also include a table that displays the loadable firmware images as found in the firmware directory on the SD Card.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ subl project-spec/meta-user/recipes-apps/website/files/cgi-bin/index.php

    index.php

    1. <!--
    2. File .......... index.php
    3. Author ........ Steve Haywood
    4. Website ....... http://www.spacewire.co.uk
    5. Project ....... SpaceWire UK Tutorial
    6. Version ....... 1.1
    7. Conception .... 8 August 2023
    8. Standard ...... PHP 7
    9. Description ...
    10.   Homepage for Zedboard Webserver.
    11. -->

    12. <?php
    13. // Get information
    14. $sys_host = exec('hostname', $retval);
    15. $sys_time = exec('date', $retval);
    16. $sys_load = exec('awk \'{print $1}\' /proc/loadavg', $retval);
    17. $sys_up = exec('awk \'{print $1}\' /proc/uptime', $retval);
    18. $cpu_model = exec('grep model /proc/cpuinfo | cut -d : -f2 | tail -1 | sed \'s/\s//\'', $retval);
    19. $cpu_cores = exec('grep -c ^processor /proc/cpuinfo', $retval);
    20. $mem_total = exec('free -m | awk \'NR==2{print $2}\'', $retval);
    21. $mem_used = exec('free -m | awk \'NR==2{print $3}\'', $retval);
    22. $mem_free = exec('free -m | awk \'NR==2{print $4}\'', $retval);
    23. $net_mac = exec('cat /sys/class/net/eth0/address', $retval);
    24. $net_ip_loc = exec('ip a | grep inet | grep -vw lo | grep -v inet6 | cut -d \/ -f1 | sed \'s/[^0-9\.]*//g\'', $retval);
    25. $net_ip_ext = exec('wget -q -O- http://ipecho.net/plain', $retval);
    26. ?>

    27. <!DOCTYPE html>
    28. <html lang="en">
    29. <head>
    30. <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    31. <link href="../styles.css" rel="stylesheet">
    32. <title>Zedboard Webserver</title>
    33. </head>
    34. <body onload="add_register()">

    35. <div class="section"><h2>Zedboard Webserver</h2></div>

    36. <div class="section">
    37. <table>
    38. <thead>
    39. <tr><th colspan="3">Operating System Information <button onclick="read_os_ids()">Read ID</button></th>
    40. </tr>
    41. </thead>
    42. <tbody>
    43. <tr>
    44. <td style="text-align:right">Description :</td>
    45. <td style="text-align:left" id="oid_0">Unknown</td>
    46. <td><img id="osid_0" style="vertical-align:middle" src="../amber.gif" title="Unknown!" alt="Missing Image!"></td>
    47. </tr>
    48. <tr>
    49. <td style="text-align:right">Company :</td>
    50. <td style="text-align:left" id="oid_1">Unknown</td>
    51. <td><img id="osid_1" style="vertical-align:middle" src="../amber.gif" title="Unknown!" alt="Missing Image!"></td>
    52. </tr>
    53. <tr>
    54. <td style="text-align:right">Author :</td>
    55. <td style="text-align:left" id="oid_2">Unknown</td>
    56. <td><img id="osid_2" style="vertical-align:middle" src="../amber.gif" title="Unknown!" alt="Missing Image!"></td>
    57. </tr>
    58. <tr>
    59. <td style="text-align:right">Version :</td>
    60. <td style="text-align:left" id="oid_3">Unknown</td>
    61. <td><img id="osid_3" style="vertical-align:middle" src="../amber.gif" title="Unknown!" alt="Missing Image!"></td>
    62. </tr>
    63. <tr>
    64. <td style="text-align:right">Timestamp :</td>
    65. <td style="text-align:left" id="oid_4">Unknown</td>
    66. <td><img id="osid_4" style="vertical-align:middle" src="../amber.gif" title="Unknown!" alt="Missing Image!"></td>
    67. </tr>
    68. <tr>
    69. <td style="text-align:right">Hash :</td>
    70. <td style="text-align:left" id="oid_5">Unknown</td>
    71. <td><img id="osid_5" style="vertical-align:middle" src="../amber.gif" title="Unknown!" alt="Missing Image!"></td>
    72. </tr>
    73. </tbody>
    74. </table>
    75. <table>
    76. <thead>
    77. <tr><th colspan="3">Firmware Information <input type="submit" value="Read ID" id="read_ids" onclick="read_ids()"></th>
    78. </tr>
    79. </thead>
    80. <tbody>
    81. <tr>
    82. <td style="text-align:right">Description :</td>
    83. <td style="text-align:left" id="id_0">Unknown</td>
    84. <td><img id="sid_0" style="vertical-align:middle" src="../amber.gif" title="Unknown!" alt="Missing Image!"></td>
    85. </tr>
    86. <tr>
    87. <td style="text-align:right">Company :</td>
    88. <td style="text-align:left" id="id_1">Unknown</td>
    89. <td><img id="sid_1" style="vertical-align:middle" src="../amber.gif" title="Unknown!" alt="Missing Image!"></td>
    90. </tr>
    91. <tr>
    92. <td style="text-align:right">Author :</td>
    93. <td style="text-align:left" id="id_2">Unknown</td>
    94. <td><img id="sid_2" style="vertical-align:middle" src="../amber.gif" title="Unknown!" alt="Missing Image!"></td>
    95. </tr>
    96. <tr>
    97. <td style="text-align:right">Version :</td>
    98. <td style="text-align:left" id="id_3">Unknown</td>
    99. <td><img id="sid_3" style="vertical-align:middle" src="../amber.gif" title="Unknown!" alt="Missing Image!"></td>
    100. </tr>
    101. <tr>
    102. <td style="text-align:right">Timestamp :</td>
    103. <td style="text-align:left" id="id_4">Unknown</td>
    104. <td><img id="sid_4" style="vertical-align:middle" src="../amber.gif" title="Unknown!" alt="Missing Image!"></td>
    105. </tr>
    106. <tr>
    107. <td style="text-align:right">Hash :</td>
    108. <td style="text-align:left" id="id_5">Unknown</td>
    109. <td><img id="sid_5" style="vertical-align:middle" src="../amber.gif" title="Unknown!" alt="Missing Image!"></td>
    110. </tr>
    111. </tbody>
    112. </table>
    113. <table>
    114. <thead>
    115. <tr><th colspan="3">Loadable Firmware</th></tr>
    116. </thead>
    117. <tbody>
    118. <?php
    119.   $dirPath = "/media/sd-mmcblk0p1/firmware";
    120.   $files = scandir($dirPath);
    121.   foreach ($files as $file) {
    122.     $filePath = $dirPath . '/' . $file;
    123.     if (is_file($filePath)) {
    124.       echo "<tr><td style=\"text-align:left\">";
    125.       echo $file;
    126.       echo "</td><td><button onclick=\"loadfirmware('$file')\">Load</button></td><td><img id=\"fm_$file\" style=\"vertical-align:middle\" src=\"../amber.gif\" title=\"Unknown!\" alt=\"Missing Image!\"></td></tr>";
    127.     }
    128.   }
    129. ?>
    130. </tbody>
    131. </table>
    132. </div>

    133. <div class="section"><img src="../zedboard.png" alt="Missing Image!"></div>

    134. <div class="section">

    135. <table>
    136. <tr><th colspan="2">System</th></tr>
    137. <tr><td>Hostname</td>
    138. <td><?php echo $sys_host ?></td>
    139. </tr><tr><td>Time</td><td><?php echo $sys_time ?></td></tr>
    140. <tr><td>Uptime</td><td><span id="uptime_text"><?php echo $sys_up ?></span> seconds <button onclick="get_uptime()">Refresh</button> Auto :
    141. <select id="uptime" onchange="uptime();">
    142.   <option value="0">Off</option>
    143.   <option value="1">1s</option>
    144.   <option value="5">5s</option>
    145. </select>
    146. </td></tr>
    147. </table>

    148. <table>
    149. <tr><th colspan="2">CPU</th></tr>
    150. <tr><td>Model</td>
    151. <td><?php echo $cpu_model ?></td></tr>
    152. <tr><td>Cores</td><td><?php echo $cpu_cores ?></td></tr>
    153. <tr><td>Load</td><td><?php echo $sys_load ?></td></tr>
    154. </table>

    155. <table>
    156. <tr><th colspan="2">Memory</th></tr>
    157. <tr><td>Total</td><td><?php echo $mem_total ?> Mb</td></tr>
    158. <tr><td>Used</td><td><?php echo $mem_used ?> Mb</td></tr>
    159. <tr><td>Free</td><td><?php echo $mem_free ?> Mb</td></tr>
    160. </table>

    161. <table>
    162. <tr><th colspan="2">Network</th></tr>
    163. <tr><td>MAC Address</td><td><?php echo $net_mac ?></td></tr>
    164. <tr><td>Internal IP</td><td><?php echo $net_ip_loc ?></td></tr>
    165. <tr><td>External IP</td><td><?php echo $net_ip_ext ?></td></tr>
    166. </table>

    167. </div>

    168. <div class="section">
    169. <table id="registers">
    170. <tr>
    171. <th>Address</th>
    172. <th>Hex</th>
    173. <th>Peek Value</th>
    174. <th>Sel</th>
    175. <th>Peek</th>
    176. <th>Status</th>
    177. <th>Copy</th>
    178. <th colspan="2">Poke Value</th>
    179. <th>Sel</th>
    180. <th>Poke</th>
    181. <th>Status</th>
    182. <th>Description</th>
    183. </tr>
    184. </table>
    185. <br><br>
    186. <input title="Add new row to end of address table" type="button" value="Add" onclick="add_row()">
    187. <select title="Set type of row to add to address table" id="type">
    188.   <option value="0">Register</option>
    189.   <option value="1">Section</option>
    190. </select>
    191. <input title="Remove last address from table" type="button" value="Remove" onclick="rem_register()">
    192. <input title="Peek all selected addresses in table" type="button" value="Peek All" onclick="peek_all()">
    193. <input title="Copy all table peek values into poke values" type="button" value="Copy All" onclick="copy_all()">
    194. <input title="Poke all selected addresses in table" type="button" value="Poke All" onclick="poke_all()">
    195. Peek Refresh :
    196. <select title="Set timer interval for automatic peek of table addresses" id="timer" onchange="timer()">
    197.   <option value="0">Off</option>
    198.   <option value="1">1s</option>
    199.   <option value="5">5s</option>
    200. </select>
    201. Configuration :
    202. <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>
    203. <input title="Read configuration file into table" type="file" id="load_config">
    204. </div>

    205. <div class="section">Designed by Steve Haywood @ 2021</div>

    206. <script src="../uptime.js"></script>

    207. </body>
    208. </html>

    17. Update Javascript

    Update the Javascript to include support for the new features; range widget, select widget, individual hex/dec display & copy section.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ subl project-spec/meta-user/recipes-apps/website/files/uptime.js

    uptime.js

    1. //
    2. // File .......... uptime.js
    3. // Author ........ Steve Haywood
    4. // Website ....... http://www.spacewire.co.uk
    5. // Project ....... SpaceWire UK Tutorial
    6. // Version ....... 1.6
    7. // Conception .... 17 January 2022
    8. // Standard ...... ECMA-262
    9. // Description ...
    10. //   Javascript functions for the dynamic webpage.
    11. //

    12. // Constants
    13. const c_uns = 0; // Unsigned string
    14. const c_hex = 1; // Hexadecimal string

    15. // Requests
    16. var timer_uptime;
    17. var timer_peek_all;

    18. // Load PL Firmware
    19. function loadfirmware(filename) {
    20.   var url = "/cgi-bin/loadfirmware?" + filename;
    21.   if (window.XMLHttpRequest) {
    22.     var ajaxReq = new XMLHttpRequest();
    23.     ajaxReq.onreadystatechange = function() {
    24.       if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
    25.         var respText = ajaxReq.responseText;
    26.         var img_obj = document.getElementById("fm_" + filename);
    27.         // Unique number is added to image to avoid caching issues on separate animations
    28.         const now = Date.now();
    29.         if (respText.substr(0,6) == "Error:") {
    30.           img_obj.src = "../red.png?" + now;
    31.           img_obj.title = "Last loadfirmware failed : " + respText.substr(7);
    32.         } else {
    33.           img_obj.src = "../green.gif?" + now;
    34.           img_obj.title = "Last loadfirmware successful";
    35.         }
    36.       }
    37.     }
    38.     ajaxReq.open("POST", url, true);
    39.     ajaxReq.send(null);
    40.   }
    41. }

    42. // Download OS information file & display result
    43. async function read_os_ids() {
    44.   let response = await fetch("/project.txt");
    45.   if (response.status == 200) {
    46.     let ids = await response.text();
    47.     fields = ids.split(/\r?\n/);
    48.   }
    49.   for (var i = 0; i < 6; i++) {
    50.     const now = Date.now();
    51.     const txt_obj = document.getElementById("oid_" + i);
    52.     const img_obj = document.getElementById("osid_" + i);
    53.     if (response.status == 200) {
    54.       if (i < fields.length && fields[i] != "") {
    55.         img_obj.src = "../green.gif?" + now;
    56.         img_obj.title = "Last file fetch successful";
    57.         txt_obj.innerHTML = fields[i];
    58.       } else {
    59.         img_obj.src = "../red.gif?" + now;
    60.         img_obj.title = "Missing field information";
    61.         txt_obj.innerHTML = "Unknown";
    62.       }
    63.     } else {
    64.       img_obj.src = "../red.gif?" + now;
    65.       img_obj.title = "Last file fetch failed";
    66.       txt_obj.innerHTML = "Unknown";
    67.     }
    68.   }
    69. }

    70. // Peek all strings
    71. function read_ids() {
    72.   read_id(0x000, 0);
    73.   read_id(0x080, 1);
    74.   read_id(0x0C0, 2);
    75.   read_id(0x100, 3);
    76.   read_id(0x120, 4);
    77.   read_id(0x140, 5);
    78. }

    79. // Peek string & display result
    80. function read_id(offset, reg) {
    81.   var url = "/cgi-bin/peekstring?0x40000000&4096&" + offset + "&128";
    82.   if (window.XMLHttpRequest) {
    83.     var ajaxReq = new XMLHttpRequest();
    84.     ajaxReq.onreadystatechange = function() {
    85.       if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
    86.         var respText = ajaxReq.responseText;
    87.         var img_obj = document.getElementById("sid_" + reg);
    88.         // Unique number is added to image to avoid caching issues on separate animations
    89.         const now = Date.now();
    90.         if (respText.substr(0,6) == "Error:") {
    91.           img_obj.src = "../red.png?" + now;
    92.           img_obj.title = "Last peekstring failed : " + respText.substr(7);
    93.         } else {
    94.           const now = Date.now();
    95.           img_obj.src = "../green.gif?" + now;
    96.           img_obj.title = "Last peekstring successful";
    97.           document.getElementById("id_" + reg).innerHTML = respText;
    98.         }
    99.       }
    100.     }
    101.     ajaxReq.open("POST", url, true);
    102.     ajaxReq.send(null);
    103.   }
    104. }

    105. // Get uptime
    106. function get_uptime(reg) {
    107.   var url = "/cgi-bin/uptime.cgi";
    108.   if (window.XMLHttpRequest) {
    109.     var ajaxReq = new XMLHttpRequest();
    110.     ajaxReq.onreadystatechange = function() {
    111.       if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
    112.         var respText = ajaxReq.responseText;
    113.         txtObj = document.getElementById("uptime_text");
    114.         if (txtObj) {
    115.           txtObj.innerHTML = respText;
    116.         }
    117.       }
    118.     }
    119.     ajaxReq.open("POST", url, true);
    120.     ajaxReq.send(null);
    121.   }
    122. }

    123. // Update uptime timer
    124. function uptime() {
    125.   clearInterval(timer_uptime);
    126.   var uptime = document.getElementById("uptime");
    127.   var interval = uptime.value;
    128.   if (interval > 0) {
    129.     timer_uptime = setInterval("get_uptime()", 1000 * interval);
    130.   }
    131. }

    132. // Update peek_all timer
    133. function timer() {
    134.   clearInterval(timer_peek_all);
    135.   var timer = document.getElementById("timer");
    136.   interval = timer.value;
    137.   if (interval > 0) {
    138.     timer_peek_all = setInterval("peek_all()", 1000 * interval);
    139.   }
    140. }

    141. // Convert unsigned/hexadecimal number string into a specified number string.
    142. function fmtUnsignedLong(string, format) {
    143.   var hexStr;
    144.   const value = parseInt(string);
    145.   switch(format) {
    146.     case c_uns:
    147.       hexStr = value.toString(10);
    148.       break;
    149.     case c_hex:
    150.       hexStr = value.toString(16).toUpperCase();
    151.       hexStr = "0x" + "00000000".substr(0, 8 - hexStr.length) + hexStr;
    152.       break;
    153.     default:
    154.       break;
    155.   }
    156.   return hexStr;
    157. }

    158. // Call fmtUnsignedLong with type setting from row in peek/poke address table.
    159. function fmtUnsignedLongReg(reg, value) {
    160.   const disp_obj = document.getElementById("display_" + reg);
    161.   var hexStr = "0xNaN";
    162.   if (disp_obj) {
    163.     const format = (disp_obj.checked) ? (c_hex) : (c_uns);
    164.     hexStr = fmtUnsignedLong(value, format);
    165.   }
    166.   return hexStr;
    167. }


    168. // Copy peek to poke & update any associated widgets
    169. function copy(reg) {
    170.   const peek_obj = document.getElementById("peek_" + reg);
    171.   if (peek_obj) {
    172.     const poke_obj = document.getElementById("poke_" + reg);
    173.     if (poke_obj) {
    174.       poke_obj.value = peek_obj.value;
    175.     }
    176.     const range_obj = document.getElementById("poke_range_" + reg);
    177.     if (range_obj) {
    178.       range_obj.value = parseInt(peek_obj.value);
    179.     }
    180.     const select_obj = document.getElementById("poke_select_" + reg);
    181.     if (select_obj) {
    182.       select_obj.value = peek_obj.value;
    183.     }
    184.   }
    185. }

    186. // Copy selected peek to poke in section
    187. function copy_section(row) {
    188.   var obj_sel;
    189.   do {
    190.     row++;
    191.     obj_sel = document.getElementById("peek_sel_" + row);
    192.     if (obj_sel) {
    193.       if (obj_sel.checked) {
    194.         copy(row);
    195.       }
    196.     }
    197.   } while (obj_sel);
    198. }

    199. // Copy all peek to poke
    200. function copy_all() {
    201.   var table = document.getElementById("registers");
    202.   var rows = table.rows.length - 1;
    203.   for (var index = 0; index < rows; index++) {
    204.     copy(index);
    205.   }
    206. }

    207. // Peek address & display result
    208. function peek(reg) {
    209.   var url = "/cgi-bin/peek?" + document.getElementById("addr_" + reg).value;
    210.   if (window.XMLHttpRequest) {
    211.     var ajaxReq = new XMLHttpRequest();
    212.     ajaxReq.onreadystatechange = function() {
    213.       if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
    214.         var respText = ajaxReq.responseText;
    215.         var img_obj = document.getElementById("speek_" + reg);
    216.         // Unique number is added to image to avoid caching issues on separate animations
    217.         const now = Date.now();
    218.         if (respText.substr(0,6) == "Error:") {
    219.           img_obj.src = "../red.gif?" + now;
    220.           img_obj.title = "Last peek failed : " + respText.substr(7);
    221.         } else {
    222.           img_obj.src = "../green.gif?" + now;
    223.           img_obj.title = "Last peek successful";
    224.           document.getElementById("peek_" + reg).value = fmtUnsignedLongReg(reg, respText);
    225.         }
    226.       }
    227.     }
    228.     ajaxReq.open("POST", url, true);
    229.     ajaxReq.send(null);
    230.   }
    231. }

    232. // Peek all selected addresses in section
    233. function peek_section(row) {
    234.   var obj_sel;
    235.   do {
    236.     row++;
    237.     obj_sel = document.getElementById("peek_sel_" + row);
    238.     if (obj_sel) {
    239.       if (obj_sel.checked) {
    240.         peek(row);
    241.       }
    242.     }
    243.   } while (obj_sel);
    244. }

    245. // Peek all selected addresses in table
    246. function peek_all() {
    247.   var table = document.getElementById("registers");
    248.   var rows = table.rows.length - 1;
    249.   for (var index = 0; index < rows; index++) {
    250.     const obj_sel = document.getElementById("peek_sel_" + index);
    251.     if (obj_sel) {
    252.       if (obj_sel.checked) {
    253.         peek(index);
    254.       }
    255.     }
    256.   }
    257. }

    258. // Poke address & display result
    259. function poke(reg) {
    260.   var url = "/cgi-bin/poke?" + document.getElementById("addr_" + reg).value + "&" + document.getElementById("poke_" + reg).value;
    261.   if (window.XMLHttpRequest) {
    262.     var ajaxReq = new XMLHttpRequest();
    263.     ajaxReq.onreadystatechange = function() {
    264.       if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
    265.         var respText = ajaxReq.responseText;
    266.         var img_obj = document.getElementById("spoke_" + reg);
    267.         // Unique number is added to image to avoid caching issues on separate animations
    268.         const now = Date.now();
    269.         if (respText.substr(0,6) == "Error:") {
    270.           img_obj.src = "../red.gif?" + now;
    271.           img_obj.title = "Last poke failed : " + respText.substr(7);
    272.         } else {
    273.           img_obj.src = "../green.gif?" + now;
    274.           img_obj.title = "Last poke successful";
    275.         }
    276.       }
    277.     }
    278.     ajaxReq.open("POST", url, true);
    279.     ajaxReq.send(null);
    280.   }
    281. }

    282. // Poke value from input widget & update any associated widgets
    283. function poke_widget(reg) {
    284.   const poke_obj = document.getElementById("poke_" + reg);
    285.   if (poke_obj) {
    286.     const range_obj = document.getElementById("poke_range_" + reg);
    287.     if (range_obj) {
    288.       range_obj.value = parseInt(poke_obj.value);
    289.     }
    290.     const select_obj = document.getElementById("poke_select_" + reg);
    291.     if (select_obj) {
    292.       select_obj.value = poke_obj.value;
    293.     }
    294.   }
    295.   poke(reg);
    296. }

    297. // Poke value on Enter key from within input widget
    298. // Holding down Enter keeps on poking which is not desired - needs improvement!
    299. function poke_key(reg) {
    300.   if (event.key === 'Enter') {
    301.     poke_widget(reg);
    302.   }
    303. }

    304. // Update radix for peek & poke input widgets
    305. function update_radix(reg) {
    306.   const poke_obj = document.getElementById("poke_" + reg);
    307.   if (poke_obj) {
    308.     poke_obj.value = fmtUnsignedLongReg(reg, poke_obj.value);
    309.   }
    310.   const peek_obj = document.getElementById("peek_" + reg);
    311.   if (peek_obj) {
    312.     peek_obj.value = fmtUnsignedLongReg(reg, peek_obj.value);
    313.   }
    314. }

    315. // Poke value from range widget & update any associated widgets
    316. function poke_range(reg) {
    317.   const range_obj = document.getElementById("poke_range_" + reg);
    318.   if (range_obj) {
    319.     const poke_obj = document.getElementById("poke_" + reg);
    320.     if (poke_obj) {
    321.       poke_obj.value = fmtUnsignedLongReg(reg, range_obj.value);
    322.     }
    323.   }
    324.   poke(reg);
    325. }

    326. // Poke value from select widget & update any associated widgets
    327. function poke_select(reg) {
    328.   const select_obj = document.getElementById("poke_select_" + reg);
    329.   if (select_obj) {
    330.     const poke_obj = document.getElementById("poke_" + reg);
    331.     if (poke_obj) {
    332.       poke_obj.value = fmtUnsignedLongReg(reg, select_obj.value);
    333.     }
    334.   }
    335.   poke(reg);
    336. }

    337. // Poke all selected addresses in section
    338. function poke_section(row) {
    339.   var obj_sel;
    340.   do {
    341.     row++;
    342.     obj_sel = document.getElementById("poke_sel_" + row);
    343.     if (obj_sel) {
    344.       if (obj_sel.checked) {
    345.         poke_widget(row);
    346.       }
    347.     }
    348.   } while (obj_sel);
    349. }

    350. // Poke all selected addresses in table
    351. function poke_all() {
    352.   var table = document.getElementById("registers");
    353.   var rows = table.rows.length - 1;
    354.   for (var index = 0; index < rows; index++) {
    355.     const obj_sel = document.getElementById("poke_sel_" + index);
    356.     if (obj_sel) {
    357.       if (obj_sel.checked) {
    358.         poke_widget(index);
    359.       }
    360.     }
    361.   }
    362. }

    363. // Add row to table
    364. function add_row() {
    365.   const obj_type = document.getElementById("type");
    366.   const row_type = obj_type.value;
    367.   switch(row_type) {
    368.     case "0":
    369.       add_register();
    370.       break;
    371.     case "1":
    372.       add_section();
    373.       break;
    374.     default:
    375.       break;
    376.   }
    377. }

    378. // Add peek/poke row to table
    379. function add_register(reg) {
    380.   const table = document.getElementById("registers");
    381.   const next = table.rows.length - 1;
    382.   var addr = 0x40010000;
    383.   if (next > 0) {
    384.     const obj_addr = document.getElementById("addr_" + (next - 1));
    385.     if (obj_addr) {
    386.       addr = parseInt(obj_addr.value) + 4;
    387.     }
    388.   }
    389.   const fields = ["reg", addr.toString(), "true", "true", "0x00000000", "true", "Register @ " + fmtUnsignedLong(addr.toString(), c_hex)];
    390.   add_row_raw(fields);
    391. }

    392. // Add section row to table
    393. function add_section(reg) {
    394.   const fields = ["sec", "Section Description"];
    395.   add_row_raw(fields);
    396. }

    397. // Add row to table
    398. function add_row_raw(fields) {
    399.   const table = document.getElementById("registers");
    400.   const next = table.rows.length - 1;
    401.   const row = table.insertRow(-1);
    402.   var newcell;

    403.   // Separate out fields
    404.   var type            = fields[0];
    405.   if (type == "sec") {
    406.     var description   = fields[1];
    407.   } else {
    408.     var address       = fields[1];
    409.     var display_type  = fields[2];
    410.     var peek_select   = fields[3];
    411.     var options       = fields[4].split("#");
    412.     var poke_select   = fields[5];
    413.     var description   = fields[6];
    414.     // Decode fields
    415.     var disp_checked  = (display_type == "true") ? ("checked") : ("");
    416.     var format        = (display_type == "true") ? (c_hex) : (c_uns);
    417.     var peek_checked  = (peek_select == "true") ? ("checked") : ("");
    418.     var poke_checked  = (poke_select == "true") ? ("checked") : ("");
    419.   }

    420.   newcell = row.insertCell(-1);
    421.   if (type == "sec") {
    422.     newcell.colSpan = "4";
    423.     newcell.innerHTML = "--- Section ---";
    424.   } else {
    425.     newcell.innerHTML = '<input title="Address to peek/poke" type="text" id="addr_' + next + '" value="' + fmtUnsignedLong(address, c_hex) + '" size="10">';

    426.     newcell = row.insertCell(-1);
    427.     newcell.innerHTML = '<input title="Select peak/poke display type (unsigned/hexadecimal)" type="checkbox" id="display_' + next + '" ' + disp_checked + ' onchange="update_radix(' + next + ')">';

    428.     newcell = row.insertCell(-1);
    429.     newcell.innerHTML = '<input title="Value peeked at address" type="text" id="peek_' + next + '" value="'+ fmtUnsignedLong("0x0", format) +'" size="10" readonly="readonly">';

    430.     newcell = row.insertCell(-1);
    431.     newcell.innerHTML = '<input title="Select address for peeking" type="checkbox" id="peek_sel_' + next + '" ' + peek_checked + '>';
    432.   }

    433.   newcell = row.insertCell(-1);
    434.   if (type == "sec") {
    435.     newcell.innerHTML = '<input title="Peek all selected addresses in section" type="submit" value="Peek" onclick="peek_section(' + next + ')">';
    436.   } else {
    437.     newcell.innerHTML = '<input title="Peek address" type="submit" value="Peek" onclick="peek(' + next + ')">';
    438.   }

    439.   newcell = row.insertCell(-1);
    440.   if (type == "sec") {
    441.     newcell.innerHTML = '<img style="vertical-align:middle" src="../amber.gif" alt="Missing Image!">';
    442.   } else {
    443.     newcell.innerHTML = '<img title="Peek status" id="speek_' + next + '" style="vertical-align:middle" src="../amber.gif" alt="Missing Image!">';
    444.   }

    445.   newcell = row.insertCell(-1);
    446.   if (type == "sec") {
    447.     newcell.innerHTML = '<input title="Copy all selected peek values into poke values in section" type="submit" value=">>" onclick="copy_section(' + next + ')">';
    448.   } else {
    449.     newcell.innerHTML = '<input title="Copy peek value into poke value" type="submit" value=">>" onclick="copy(' + next + ')">';
    450.   }

    451.   newcell = row.insertCell(-1);
    452.   switch (type) {
    453.     case "sec":
    454.       newcell.colSpan = "3";
    455.       newcell.innerHTML = "--- Section ---";
    456.       break;
    457.     case "reg":
    458.       const poke_value    = fields[4];
    459.       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 + ')">';
    460.       newcell = row.insertCell(-1);
    461.       newcell.style.padding = "0";
    462.       break;
    463.     case "range":
    464.       var range_value   = options[0];
    465.       var range_min     = options[1];
    466.       var range_max     = options[2];
    467.       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 + ')">';
    468.       newcell = row.insertCell(-1);
    469.       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 + ')">';
    470.       break;
    471.     case "select":
    472.       var opt_str = "";
    473.       var opt_sel;
    474.       var opt_pair;
    475.       const sel = options[0];
    476.       for (var index = 1; index < options.length; index++) {
    477.         opt_pair = options[index].split("^");
    478.         opt_sel = (sel == index - 1) ? (" selected") : ("");
    479.         opt_str = opt_str.concat('<option value="', opt_pair[0], '"', opt_sel + '>', opt_pair[1], '</option>');
    480.       }
    481.       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 + ')">';
    482.       newcell = row.insertCell(-1);
    483.       newcell.innerHTML = '<select title="Value to poke at address" style="width:100%" id="poke_select_' + next + '" onchange="poke_select(' + next + ')">' + opt_str + '</select>';
    484.       break;
    485.     default:
    486.       break;
    487.   }

    488.   if (type == "sec") {
    489.     newcell = row.insertCell(-1);
    490.     newcell.innerHTML = '<input title="Poke all selected addresses in section" type="submit" value="Poke" onclick="poke_section(' + next + ')">';

    491.     newcell = row.insertCell(-1);
    492.     newcell.innerHTML = '<img style="vertical-align:middle" src="../amber.gif" alt="Missing Image!">';

    493.     newcell = row.insertCell(-1);
    494.     newcell.innerHTML = '<input title="Description of section" type="text" id="name_' + next + '" value="' + description + '" size="40">';
    495.   } else {
    496.     newcell = row.insertCell(-1);
    497.     newcell.innerHTML = '<input title="Select address for poking" type="checkbox" id="poke_sel_' + next + '" ' + poke_checked + '>';

    498.     newcell = row.insertCell(-1);
    499.     newcell.innerHTML = '<input title="Poke address" type="submit" value="Poke" onclick="poke_widget(' + next + ')">';

    500.     newcell = row.insertCell(-1);
    501.     newcell.innerHTML = '<img title="Poke status" id="spoke_' + next + '" style="vertical-align:middle" src="../amber.gif" alt="Missing Image!">';

    502.     newcell = row.insertCell(-1);
    503.     newcell.innerHTML = '<input title="Description of address" type="text" id="name_' + next + '" value="' + description + '" size="40">';
    504.   }
    505. }

    506. // Remove row from table
    507. function rem_register(reg) {
    508.   var table = document.getElementById("registers");
    509.   if (table.rows.length > 1) {
    510.     table.deleteRow(-1);
    511.   }
    512. }

    513. // Remove all rows from table
    514. function remove_all() {
    515.   var table = document.getElementById("registers");
    516.   var rows = table.rows.length - 1;
    517.   for (var index = 0; index < rows; index++) {
    518.     table.deleteRow(-1);
    519.   }
    520. }

    521. // Note: browser file access is made difficult for security reasons - there maybe a better way of doing file read & write.

    522. var config_file = null;

    523. // Create virtual configuration file from address table for user to download
    524. function create_config() {
    525.   var text = "";
    526.   var table = document.getElementById("registers");
    527.   const rows = table.rows.length - 1;
    528.   for (var index = 0; index < rows; index++) {
    529.     const obj_addr = document.getElementById("addr_" + index);
    530.     if (obj_addr) { // Register type
    531.       var addr = document.getElementById("addr_" + index).value;
    532.       var display = document.getElementById("display_" + index).checked;
    533.       var peek_sel = document.getElementById("peek_sel_" + index).checked;
    534.       var poke = document.getElementById("poke_" + index).value;
    535.       var poke_sel = document.getElementById("poke_sel_" + index).checked;
    536.       var name = document.getElementById("name_" + index).value;
    537.       text += "reg" + "|" + addr + "|" + display + "|" + peek_sel + "|" + poke + "|" + poke_sel + "|"+ name + "\n";
    538.     } else { // Section type
    539.       var name = document.getElementById("name_" + index).value;
    540.       text += "sec" + "|" + name + "\n";
    541.     }
    542.   }
    543.   const data = new Blob([text], {type: 'text/plain'});
    544.   if (config_file !== null) {
    545.     URL.revokeObjectURL(config_file);
    546.   }
    547.   config_file = URL.createObjectURL(data);
    548.   var link = document.getElementById('download');
    549.   link.href = config_file;
    550.   link.style.display = 'inline';
    551. }

    552. // Read configuration file and update address table
    553. function load_config(input) {
    554.   var file = input.target.files[0];
    555.   if (file) {
    556.     var reader = new FileReader();
    557.     reader.onload = function(input) {
    558.       var contents = input.target.result;
    559.       const lines = contents.split(/\r\n|\n/);
    560.       remove_all();
    561.       lines.forEach((line) => {
    562.         if (line.length > 0) {
    563.           var table = document.getElementById("registers");
    564.           var next = table.rows.length - 1;
    565.           const values = line.split("|");
    566.           switch(values[0]) {
    567.             case "reg":
    568.             case "sec":
    569.             case "range":
    570.             case "select":
    571.               add_row_raw(values);
    572.               break;
    573.             default:
    574.               alert("Error: Unrecognized table type found (" + values[0] + "), ignoring.");
    575.           }
    576.         }
    577.       });
    578.     };
    579.     reader.readAsText(file);
    580.   }
    581. }

    582. document.getElementById('load_config').addEventListener('change', load_config, false);

    18. Check everything is working as expected

    Copy the updated HTML & Javascript files over to PetaLinux.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ sshpass -p root scp project-spec/meta-user/recipes-apps/website/files/cgi-bin/index.php root@192.168.2.87:/srv/www/cgi-bin
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ sshpass -p root scp project-spec/meta-user/recipes-apps/website/files/uptime.js root@192.168.2.87:/srv/www
    Access the webserver running on the Zedboard using a browser pointing at the Zedboard's IP address (192.168.2.87/cgi-bin/index.php). All being well something akin to the following webpage should be displayed. Missing Image! Update the LEDs & Switches configuration file to include the newly created range & select HTML widgets.

    Note the changes :-
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ subl ../../os/src/other/zedboard_leds_switches.txt

    zedboard_leds_switches.txt

    1. sec|AXI General Purpose IO - Zedboard Specific
    2. range|0x40010000|false|true|0#0#255|true|LEDs
    3. reg|0x40010008|true|true|0x00000000|false|DIP Switches
    4. reg|0x40010010|true|true|0x00000000|false|Push Buttons
    5. select|0x4001011C|true|true|0#0x00000000^Disabled#0x80000000^Enabled|true|Interrupt Enable
    6. select|0x40010128|false|true|0#0^No Channel#1^Channel 1 (LEDs)#2^Channel 2 (DIP Switches)#4^Channel 3 (Push Buttons)|true|Interrupt Enable
    7. select|0x40010120|false|true|0#0^No Channel#1^Channel 1 (LEDs)#2^Channel 2 (DIP Switches)#4^Channel 3 (Push Buttons)|true|Interrupt Status

    8. sec|AXI Register Bank
    9. reg|0x40020000|true|true|0x00000000|true|Register 0
    10. reg|0x40020004|true|true|0x456789AB|true|Register 1
    11. reg|0x40020008|true|true|0x00000000|true|Register 2
    12. reg|0x4002000C|true|true|0x0000FF00|true|Register 3

    Use the Browse button on the Webpage to select the updated zedboard_leds_switches.txt from /home/steve/os/src/others. All being well a more user friendly peek/poke address interface should be displayed.

    The video below shows the following sequence of evens.
    1. LEDs counting up from 0 (none illuminated) to 255 (all illuminated).
    2. DIP switches 0 & 7 changing from off to on.
    3. Interrupts being enabled for channel 3 (push buttons).
    4. Centre push button being pressed to trigger an interrupt.
    5. Triggered interrupt and button state being cleared.
    6. Interrupts being disabled.
    7. Left push button being pressed (no interrupt this time).
    #### Part 4 - Revisions Control ####

    19. Commit new & updated files

    Remove legacy configuration file & add new load firmware source file.
    steve@Desktop:~/projects/zedboard_linux$ git rm os/src/other/axi_gpio_zed.txt
    steve@Desktop:~/projects/zedboard_linux$ git add os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/loadfirmware.c
    Check GIT status to make sure all is well and there are no spurious elements.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ cd ../..
    steve@Desktop:~/projects/zedboard_linux$ git status
    On branch master
    Your branch is up-to-date with 'origin/master'.

    Changes to be committed:
      (use "git restore --staged <file>..." to unstage)
            new file:   os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/loadfirmware.c
            renamed:    os/petalinux/components/yocto/layers/meta-openembedded/meta-webserver/recipes-httpd/apache2/apache2_%.bbappend -> os/petalinux/project-spec/meta-user/recipes-httpd/apache2/apache2_%.bbappend
            deleted:    os/src/other/axi_gpio_zed.txt

    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:   os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/Makefile
            modified:   os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/peekpokecgi.bb
            modified:   os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/index.php
            modified:   os/petalinux/project-spec/meta-user/recipes-apps/website/files/project.txt
            modified:   os/petalinux/project-spec/meta-user/recipes-apps/website/files/uptime.js
            modified:   os/src/other/zedboard_leds_switches.txt

    Looks good!

    Commit the updates, create an annotated tag and push the commit & tag up to the remote repository.
    steve@Desktop:~/projects/zedboard_linux$ git commit -am "Added the ability to load new PL firmware without a rebuild or reboot of PetaLinux. Updated the peek/poke address table to include range & select HTML elements."
    steve@Desktop:~/projects/zedboard_linux$ git push
    steve@Desktop:~/projects/zedboard_linux$ git tag -a v13.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 with XSA from zedboard_leds_switches v5.0"
    steve@Desktop:~/projects/zedboard_linux$ git push origin v13.0

    20. Final checks

    Rebuild PetaLinux to include the updated GIT status, package it and deploy on the Zedboard.
    steve@Desktop:~/projects/zedboard_linux$ cd os/petalinux
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ petalinux-build
    steve@Desktop:~/projects/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
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ sshpass -p root scp /tftpboot/{BOOT.BIN,boot.scr,image.ub} root@192.168.2.87:/media/sd-mmcblk0p1
    root@petalinux:~# reboot
    Access the webserver running on the Zedboard using a browser pointing at the Zedboard's IP address (192.168.2.87/cgi-bin/index.php). Click the Read ID buttons in both the Operating System Information & Firmware Information sectons to read the Identification information from the OS filesystem & PL address space. All being well the following should be displayed in the Operating System Information & Firmware Information sections.

    There should be no unsavoury comments after the version numbers!