Open Memotech-Bill opened 4 months ago
Looking at the signon messages, Richard's build has:
BBC BASIC for Pico Console v0.46, Build Jul 2 2024, USB Console, UART Console, Flash Filesystem, cyw43=gpio, SDL Sound, /dev/uart, Stack Check 4, RTC
Whereas my build has:
BBC BASIC for Pico W Console v0.46, Build Jul 3 2024, USB Console, UART Console, Flash Filesystem, cyw43=background, SDL Sound, /dev/uart, Min Stack, Stack Check 4, RTC
From this, I guess that Richard is building from the console/pico
folder not the console/pico_w
folder. Specifying BOARD=pico+w
is not sufficient to enable WiFi support.
From this, I guess that Richard is building from the
console/pico
folder not theconsole/pico_w
folder.
That's correct. My understanding has always been that the various build options are achieved by specifying them in the make command line. Indeed, referring back to the issue where this was previously discussed, these were the instructions you gave for building a Pico W version:
Then build the experimental version:
cd console/pico
make BOARD=pico_w CYW43=Y
Apart from the change to BOARD=pico+w
that's what I am still doing.
That's correct. My understanding has always been that the various build options are achieved by specifying them in the make command line. Indeed, referring back to the issue where this was previously discussed, these were the instructions you gave for building a Pico W version:
That is correct in principle. Doing make in the different folders just selects different default values.
However, as noted later in that thread:
* The CYW43 switch now supports a number of options to select the different SDK libraries: * CYW43=GPIO - Only support the GPIO functions (the onboard LED and power options). This version has the same amount of BASIC memory as the standard Pico build. It could become the default build, replacing the version for the standard Pico. * CYW43=POLL Use the cyw43_arch_poll library. Note that this does not eliminate callback functions. The callbacks are triggered when you call cyw43_arch_poll(), rather thai in response to a timer interrupt. * CYW43=BACKGROUND. Use my fixed version of the cyw43_arch_threadsafe_background library. I believe that this is the most convenient version to use with BASIC.
Looking at the CMakeLists.txt file, CYW43=Y
should be equivalent to CYW43=BACKGROUND
, so I am not quite sure why your build does not include network support. Are you still doing git checkout lwip_raw_tcp
? That was an experimental branch for initial development of networking, and does not contain all the latest revisions.
Building in the console/pico_w
folder was documented at the end of that thread.
However, as noted later in that thread:
Hmm, "later in the thread" is only useful if there was a reason for me to read it, and there probably wasn't!
Are you still doing
git checkout lwip_raw_tcp
?
I don't think so..
Most of my BBCSDL builds are as 'idiot proof' as I can make them (using a bog-standard makefile). Unfortunately there are two that aren't - Android and Pico - because in each case I'm reliant on a build process that I'm not familiar with: Android Studio in the first case and CMake in the second.
So in those cases I just blindly follow instructions that I've written down from a previous occasion and seem to work. I don't know why I'm doing what I'm doing, and unless some error message or obvious malfunction results, I am none the wiser.
To cut to the chase, what should I be doing to build the most 'universal' version, with support for the Pico W but with the minimum degree of impact (e.g. in respect of available memory) when it's running on a non-W Pico?
To cut to the chase, what should I be doing to build the most 'universal' version, with support for the Pico W but with the minimum degree of impact (e.g. in respect of available memory) when it's running on a non-W Pico?
That depends upon whether or not you want network support. For a version that will run on either Pico or Pico W, but without network support:
cd PicoBB
git pull --recurse-submodules
git checkout master
cd console/pico
make BBC_SRC=<path to your source>
For a version that will run on both Pico and Pico W, and has network support on the Pico W, at the cost of reduced memory available to BASIC on either host, replace cd console/pico
with cd console/pico_w
.
A few words of explanation:
git pull
will download from GitHub any changes I have made to my repository. The --recurse-submodules
means that any changes to your repository, or the LittleFS repository, will also be downloaded.
get checkout master
ensures that you are on the master branch. You probably will be anyway, this is just a precaution.
make
builds the program. The option BBC_SRC=<path to your source>
is only necessary if you have a local copy of your source that contains changes that you have not yet committed to GitHub.
As a check, after building the program, you can use the command:
picotool info <filename.uf2>
to show the signon message without having to load the program into a Pico. The significant part is the cyw43=...
string. cyw43=gpio
indicates support for the Pico W LED, while cyw43=background
indicates network support (as well as the Pico W LED).
That depends upon whether or not you want network support.
I did say 'universal' version, so naturally that means it needs to support the key feature of the Pico W, i.e. WiFi (which I assume is what the W stands for). Other differences between the non-W and W versions are largely incidental as far as I'm concerned, and I would expect that they can be handled within, for example, a GPIO library.
How far did you get in making the memory costs of networking at least partly conditional on actually using the socklib library? The sort of thing I would expect is that calling PROC_initsockets
would rejig the memory map, moving HIMEM down to make space for the networking buffers, before enabling the 'background' functionality.
Ideally (but less important, because one can always restart the Pico) calling PROC_exitsockets
should disable the background functionality and then restore the memory map. But if that's not practical or too difficult so be it.
cd PicoBB git pull --recurse-submodules git checkout master cd console/pico make BBC_SRC=
That's similar to what I do, except that I think I use git clone
rather than git pull
, I don't know if that is significant.
I don't use the BBC_SRC=
option because at one point it didn't work properly, so my build includes a step in which I replace the source files in your cloned repository with mine. It may well be that I could use that option now.
I also explicitly replace the example programs and libraries with my versions, because commonly I will have made recent changes that aren't reflected in your repository.
That depends upon whether or not you want network support.
I did say 'universal' version, so naturally that means it needs to support the key feature of the Pico W, i.e. WiFi (which I assume is what the W stands for). Other differences between the non-W and W versions are largely incidental as far as I'm concerned, and I would expect that they can be handled within, for example, a GPIO library.
The LED on the Pico W is controlled by a GPIO on the CYW43 chip, not a GPIO on the RP2040. It requires a different set of SDK routines to control this. If these are not present, no BBC BASIC library could control the Pico W LED.
How far did you get in making the memory costs of networking at least partly conditional on actually using the socklib library? The sort of thing I would expect is that calling
PROC_initsockets
would rejig the memory map, moving HIMEM down to make space for the networking buffers, before enabling the 'background' functionality.
All such memory allocation is deep within the lwip library. It is not obvious how one would donate some memory from that available to BBC BASIC.
cd PicoBB git pull --recurse-submodules git checkout master cd console/pico make BBC_SRC=
That's similar to what I do, except that I think I use
git clone
rather thangit pull
, I don't know if that is significant. I don't use theBBC_SRC=
option because at one point it didn't work properly, so my build includes a step in which I replace the source files in your cloned repository with mine. It may well be that I could use that option now.
git clone
downloads a copy of the entire repository, whereas git pull
only downloads any changes sice the previous clone or pull.
The LED on the Pico W is controlled by a GPIO on the CYW43 chip, not a GPIO on the RP2040. It requires a different set of SDK routines to control this. If these are not present, no BBC BASIC library could control the Pico W LED.
Is that not very similar to the difference between the GPIO on the RPi 1-4 and the GPIO on the RPi 5? On the earlier models the GPIO is driven by the main SoC chip, but on the RPi 5 it's driven by a separate chip which communicates with the SoC using the I²C bus. The software interfaces are completely different, but I don't need to build different versions of BBC BASIC for the different models, it's entirely handled within the gpiolib.bbc library.
If you're saying that it's impossible for a single UF2 to be able to drive the GPIO on both the original Pico and the Pico W, I don't understand why that would be the case. Surely a UF2 could contain the drivers for both and a (notional) GPIO library use whichever interface is appropriate for the model on which it is running, similar to how it works on the Raspberry Pi itself?
If it really is impossible, it makes the whole idea of a 'universal' UF2 moot.
All such memory allocation is deep within the lwip library. It is not obvious how one would donate some memory from that available to BBC BASIC.
I thought that the CPU in the Pico was a relatively simple 'microcontroller' type device, lacking the kind of hardware memory-management which could enforce such partitioning. In its absence, I don't see how anything in the system could 'know' that BASIC is sharing the 'network' memory, so long as any background processes also accessing that memory are disabled.
If you examine the memory map of the WiFi-enabled version, is the memory allocated to networking contiguous with the memory that BASIC uses? If it is, or it can be persuaded to be (e.g. by controlling the order in which various modules are initialised) can't you disable the background processes and simply allow BASIC to extend its memory into that region. How could anything know you'd done that?
The LED on the Pico W is controlled by a GPIO on the CYW43 chip, not a GPIO on the RP2040. It requires a different set of SDK routines to control this. If these are not present, no BBC BASIC library could control the Pico W LED.
Is that not very similar to the difference between the GPIO on the RPi 1-4 and the GPIO on the RPi 5? On the earlier models the GPIO is driven by the main SoC chip, but on the RPi 5 it's driven by a separate chip which communicates with the SoC using the I²C bus. The software interfaces are completely different, but I don't need to build different versions of BBC BASIC for the different models, it's entirely handled within the gpiolib.bbc library.
If you're saying that it's impossible for a single UF2 to be able to drive the GPIO on both the original Pico and the Pico W, I don't understand why that would be the case. Surely a UF2 could contain the drivers for both and a (notional) GPIO library use whichever interface is appropriate for the model on which it is running, similar to how it works on the Raspberry Pi itself?
I think we are talking at cross purposes. On the Pico, the LED is controlled by one of the RP2040 GPIO's. On the Pico W, that GPIO is repurposed to control the CYW43 chip. The LED is then controlled by one of the extra GPIOs on the CYW43 chip, which do not exist on the Pico. The option CYW43=GPIO
(or CYW43=BACKGROUND
) selects including the drivers for the CYW43 GPIO, necessary to control these additional GPIO, in order to provide a "Universal" UF2.
All such memory allocation is deep within the lwip library. It is not obvious how one would donate some memory from that available to BBC BASIC.
I thought that the CPU in the Pico was a relatively simple 'microcontroller' type device, lacking the kind of hardware memory-management which could enforce such partitioning. In its absence, I don't see how anything in the system could 'know' that BASIC is sharing the 'network' memory, so long as any background processes also accessing that memory are disabled.
If you examine the memory map of the WiFi-enabled version, is the memory allocated to networking contiguous with the memory that BASIC uses? If it is, or it can be persuaded to be (e.g. by controlling the order in which various modules are initialised) can't you disable the background processes and simply allow BASIC to extend its memory into that region. How could anything know you'd done that?
Moving HIMEM
down makes space available right in the middle of the RAM used by BASIC, between the program and CPU stack.
I am not sure without looking whether the lwip network buffer allocation is static or dynamic.
If it is static, then the compiler will locate the buffers within the .bss
region. Even if it could be arranged that the buffers are at the end of the region, and that this region was adjacent to the BASIC region, then in order to move this memory into or out of that available to BASIC, it would be necessary to dynamically adjust PAGE
, not HIMEM
. I don't think that can be done while a program is running.
If they are dynamically allocated, then they will be on the C heap. Either the heap is located below BASIC RAM, in which case the same issue as for static buffers arises, or the entire heap is located at HIMEM. Even then, moving HIMEM down would introduce space below the existing heap, but the heap grows upwards, not down. Also, the socklib library would be sitting between the original C heap and the freed space.
The only way I can see to make what you suggest work would be to modify the lwip code to use some form of custom memory allocation for the network buffers, making use of space either freed at HIMEM
or perhaps allocated as BASIC variables. I am not currently inclined to undertake that.
The current RAM memory map for the console/pico_w build is:
I think we are talking at cross purposes.
Let me try to clarify things with a couple of simple questions:
CYW43=GPIO
switch? Surely it's easier and less confusing to include it in every build as standard, rather than making it optional.The only way I can see to make what you suggest work would be to modify the lwip code to use some form of custom memory allocation for the network buffers,
That would be the best way, certainly; other approaches are likely to involve some nasty assembly-language tricks to move the CPU's stack around without it noticing!
Obviously I've not seen the code itself, but I'm surprised that you don't think it's worth attempting. If we are still talking in terns of 40K of memory, which I think is what you originally suggested, that's a large amount to lose 'permanently' when it's only actually needed whilst socklib is in use.
- Is it possible to create a 'universal' UF2 which contains code for driving both the Pico W GPIO (including the LED) and the non-W Pico GPIO (selected at runtime if necessary)?
Yes
- If that is possible, why make the inclusion of the Pico W driver code controllable by the
CYW43=GPIO
switch? Surely it's easier and less confusing to include it in every build as standard, rather than making it optional.
The makefiles specify CYW43=GPIO or CYW43=BACKGROUND (which includes GPIO) by default. It is necessary to explicitly specify CYW43=NONE to omit the driver.
Obviously I've not seen the code itself, but I'm surprised that you don't think it's worth attempting. If we are still talking in terns of 40K of memory, which I think is what you originally suggested, that's a large amount to lose 'permanently' when it's only actually needed whilst socklib is in use.
lwip is a large and complex library. It will take some research to even work out what and how memory is allocated. Depending on how the code is structured it may be necessary to fork the entire library in order to change the memory allocation, with subsequent support issues.
Also it is not clear that the network buffers are the main issue. It may be that much of the memory loss is due to the background processing of the CYW43 full driver.
I will do a bit more research.
It is necessary to explicitly specify CYW43=NONE to omit the driver.
OK. But - just so I'm absolutely clear - is that only if built in the console/pico_w
directory or is it also included by default in the console/pico
directory? I still haven't entirely got my head around the reason for having two different directories rather than a simple build option to enable networking, like all the other options.
lwip is a large and complex library. It will take some research to even work out what and how memory is allocated.
I don't doubt it, but even a complex library may well allocate the majority of its static and dynamic buffers quite near the start, and thereafter only refer to them symbolically (e.g. as a pointer), so it might not be complicated.
Also it is not clear that the network buffers are the main issue. It may be that much of the memory loss is due to the background processing of the CYW43 full driver.
Indeed, but if the background processing is completely disabled except for when socklib is active, they can hopefully all be lumped together.
It is necessary to explicitly specify CYW43=NONE to omit the driver.
OK. But - just so I'm absolutely clear - is that only if built in the
console/pico_w
directory or is it also included by default in theconsole/pico
directory? I still haven't entirely got my head around the reason for having two different directories rather than a simple build option to enable networking, like all the other options.
I currently maintain four builds:
For me it is most convenient to have separate folders for these. I simply have to do make
without any parameters in each of console/pico
, console/pico_w
, bin/pico
and bin/pico_w
.
The "No Network" builds are retained because they do have more BASIC memory available than the "With Network" builds.
If you look at the Makefile
in each of these folders, they are quite simple, they simply set the default value for each of the required parameters and then invoke src/pico/Makefile
.
I currently maintain four builds:
Fair enough, but I only distribute a single 'as universal as possible' build for the Pico (just as I do for all the other platforms) and it's the build most people are going to come across - it seems to be what Paul Porcelijn is using.
The "No Network" builds are retained because they do have more BASIC memory available than the "With Network" builds.
Which of course is exactly what I am trying to avoid. It's not really viable to have to load a different UF2 into the Pico in order to run a BASIC program that uses network access and then revert back to the original UF2 in order to run a program that has a large memory usage.
In an ideal world it should be possible to write, for example, an application which initially accesses the network in order to download some data files and then performs some processing on that data which needs a lot of memory.
I currently maintain four builds:
Fair enough, but I only distribute a single 'as universal as possible' build for the Pico (just as I do for all the other platforms) and it's the build most people are going to come across - it seems to be what Paul Porcelijn is using.
Which is currently the build in the console/pico_w
folder. Although I have had support requests for the other builds.
The "No Network" builds are retained because they do have more BASIC memory available than the "With Network" builds.
Which of course is exactly what I am trying to avoid. It's not really viable to have to load a different UF2 into the Pico in order to run a BASIC program that uses network access and then revert back to the original UF2 in order to run a program that has a large memory usage.
I have identified in the memory map a fairly large static allocation associated with network buffers:
COMMON 0x200059a8 0x98c7 CMakeFiles/bbcbasic.dir/home/pi/pico/pico-sdk/lib/lwip/src/core/memp.c.obj
0x200059a8 memp_memory_RAW_PCB_base
0x20005a1c memp_memory_TCP_SEG_base
0x20005c20 memp_memory_PBUF_POOL_base
0x2000ebc4 memp_memory_PBUF_base
0x2000ecc8 memp_memory_TCP_PCB_LISTEN_base
0x2000edac memp_memory_REASSDATA_base
0x2000ee50 memp_memory_UDP_PCB_base
0x2000eed4 memp_memory_TCP_PCB_base
0x2000f20c memp_memory_SYS_TIMEOUT_base
At present I have not worked out how to replace this.
Even if I do manage to modify the lwip code it is not clear where within the BASIC memory I can allocate storage,
- socklib subroutines cannot change HIMEM.
Subroutines can change HIMEM but what they can't (easily and safely) do is change BASIC's stack pointer whilst it is in use!
But the key thing is easily and safely, libraries are allowed to (and the ones I write often do) break the normal rules of good coding practice, because they are 'black boxes' that end users shouldn't be fiddling with.
Here's a procedure that does succeed in lowering HIMEM, and BASIC's stack pointer, to free up some memory (here 10,000 bytes) which could be used for networking buffers:
DEF PROCtest
LOCAL b%%, d%%, s%%
DIM s%% LOCAL TRUE, b%% LOCAL 10000, d%% LOCAL TRUE
WHILE s%% < HIMEM
?d%% = ?s%%
d%% += 1 : s%% += 1
ENDWHILE
HIMEM = d%%
ENDPROC
Which commands removes data from the heap? CLEAR, NEW, any others?
CHAIN.
Here's a procedure that does succeed in lowering HIMEM, and BASIC's stack pointer, to free up some memory (here 10,000 bytes) which could be used for networking buffers:
And suppose some intermediate subroutine has taken the address of a LOCAL variable?
And suppose some intermediate subroutine has taken the address of a LOCAL variable?
Give me an example, I can't see how it could fail so long as that code is the first thing in the procedure.
It could be useful to return the original value of HIMEM to the caller, for example so you can restore it later:
DEF PROCtest(RETURN s%%)
LOCAL b%%, d%%
DIM s%% LOCAL TRUE, b%% LOCAL 10000, d%% LOCAL TRUE
WHILE s%% < HIMEM
?d%% = ?s%%
d%% += 1 : s%% += 1
ENDWHILE
HIMEM = d%%
ENDPROC
In case it isn't obvious, the reserved memory for the networking buffer is at (the new value of) HIMEM, not at b%%
.
I have managed to find a way to reclaim 38KB of memory for BASIC if networking is available but not used.
In order to make it work, it requires the following code added to routine clear() in file bbmain.c in your repository:
#ifdef PICO
pico_clear ();
#endif
With my code modification, buffers used for networking are allocated on the BASIC heap in a similar way to BASIC strings. Unused buffers are recycled, but the allocated space is not freed until an explicit or implicit CLEAR. As a consequence of this, CLEAR also closes all network connections.
Looking at the revised memory map it is not clear that any further memory can be reclaimed.
During development it was a nasty shock to find that the value of the BASIC stack pointer (esp
) needed to check for available space was not valid in routines called back from LWIP. This was caused by the variable being declared as a register variable in BBC.h. This was eventually resolved by adding the compiler switches -ffixed-r10 -ffixed-r11
so that the LWIP code did not use these registers.
More testing is needed. Should you wish to test, then git clone
or git pull
the latest version of my repository, then in folder PicoBB/console/pico_w run the command make NET_HEAP=1
.
I have managed to find a way to reclaim 38KB of memory for BASIC if networking is available but not used.
Excellent, I was hopeful that should be possible.
With my code modification, buffers used for networking are allocated on the BASIC heap
Unfortunately I don't think that will work, because one needs to be able to free the memory used for the buffers by calling PROC_exitsockets, so that for example the rest of the program can use that memory for processing data that it has previously downloaded from the network. If the buffers are allocated 'midway' through the heap there's no way of freeing it other than using CLEAR, which will likely throw away the very data that has been downloaded!
The sort of program structure that ideally needs to be supported is as follows:
PROC_initsockets : REM Initialise network access, including allocating buffers
REM. Download data from the net, for example into an array created on the heap
PROC_exitsockets : REM Free network buffers and close down network access
REM. Process the downloaded data, if necessary using the freed buffer memory.
It's a classic problem when your only options are to allocate buffers either on the heap or the stack, which is why I suggested allocating them by lowering HIMEM (for example using the code I recently published, and now documented at the Wiki). Memory above HIMEM is also better protected against accidental corruption (it's only too easy to splat the heap in BBC BASIC) and it would avoid the necessity to change bbmain.c.
This does of course still impose the limitation that PROC_initsockets and PROC_exitsockets can only safely be called when the stack is empty, i.e. from the 'main program', but that's not an uncommon limitation for libraries.
Another potential advantage is that it might allow for sockets to be kept open during a CHAIN (which calls CLEAR), in exactly the same way as files are kept open during CHAIN. This can allow multiple modules to access an open file or socket by passing its handle to the CHAINed module in a static integer variable.
Hopefully it will be as easy to allocate the buffers by lowering HIMEM as on the heap,
What is wrong with the following?
Program 1:
Download data from network
Write data to file
Chain to program 2
Program 2:
Read data from file
Process data
Since CHAIN clears variables, all memory is available for the analysis in program 2.
The problem is that the network code actually performs multiple small allocations while reading and writing network sockets.
Admittedly, the standard Pico SDK implementation (when using background processing) reserves a fixed static pool of memory, with size determined by constants in lwipopts.h, and then allocates memory out of this pool. This has two disadvantages:
If memory is allocated by moving HIMEM there are two options:
For a LOCAL structure, what is stored on the BASIC stack?
If either the format or data block, while these will be moved when the stack is moved, the pointers in the name block will not be updated with the new position.
What is wrong with the following?
I wouldn't say there is anything "wrong" with it, it's just messy and not something that a BBC BASIC programmer would expect to be necessary.
Admittedly, the standard Pico SDK implementation (when using background processing) reserves a fixed static pool of memory, with size determined by constants in lwipopts.h, and then allocates memory out of this pool. This has two disadvantages...
But are we really in the business of trying to 'improve' on what the "standard Pico SDK implementation" does (which is presumably what currently happens in the Pico W build)?
- Allocate a fixed memory pool in PROCinitsockets. This has the disadvantages listed above.
It has those disadvantages, but it also has the advantages I described. You may take a different view, but it seems to me that the advantages outweigh the disadvantages. For example it doesn't matter too much if the static allocation is a little bigger than the total of the dynamic ones, if it can be freed for other purposes by calling PROC_exitsockets.
- Move HIMEM and the BASIC stack down a bit more every time the network code needs more memory.
I entirely agree that's not acceptable, not least because of all the stack copying involved. One would want to allocate it all in one go, preferably at a time when the stack isn't in use, which is likely to be the case when PROC_initsockets is called. This is commonly the code that would be used, near the beginning of the program:
INSTALL @lib$ + "socklib"
PROC_initsockets
which pretty much guarantees that the stack won't be in use at the time.
If you really can't do it any other way I'll live with the heap allocation, but I'm not prepared to change bbmain.c, not least because in no version of BBC BASIC, right back to the original in 1981, has CLEAR ever had any 'side effects'. It would be breaking new ground, which is just the sort of thing I'm keen to avoid.
Therefore it would have to be calling PROC_exitsockets, not CLEAR, that closes down the network and releases, but doesn't free, the buffers. This is really no more dangerous than any of the other existing libraries that will leave the system in an unstable state if the associated 'cleanup' function isn't called before the program is terminated.
I would just want to add, though, that the interpreter makes assumptions about when the heap can and can't grow. This is particularly the case when 'long strings' (and in the case of the Pico version that's not very long!) are stored temporarily in the unallocated part of the heap (that is, the pointer to free memory isn't raised). This would rule out your code allocating heap memory in an asynchronous callback.
Your code PROCtest
copies down the contents of the BASIC stack, but as far as I can see it does nothing to actually move the stack pointer to point to the new stack location.
I have tried doing this from within C code. Testing with lanchat which calls PROC_initsockets
at the start, and with my version of PROC_initsockets
beginning:
REM Initialise the BBCSDL Sockets interface
DEF PROC_initsockets(N%)
DEF PROC_initsockets
LOCAL chan%, err%
SYS "net_init"
I find that when net_init()
is entered, himem
and esp
differ by 56 bytes, so even in the case where PROC_initsockets
is called from the start of the main program there is a non-trivial BASIC stack.
In C routine net_init()
, I can move himem
, copy the BASIC stack to the new location and update esp
. However in routine xeq(): case TSYS
(in bbexec.c) the value of esp
is remembered in a local variable on entry, and restored after return from my net_init()
routine. So, by the time execution returns to BASIC, the stack pointer is pointing to the original copy of the stack, not the moved one.
The only way I can see of making this work is if the code for xeq(): case THIMEML
was modified to relocate the BASIC stack whenever HIMEM is changed. Or make the variable oldesp
in xeq ()
a global variable, rather than a local one, so that my net_init()
code can set the value of esp
that is eventually returned.
As a side issue, I think the C variable esp
is confusingly / incorrectly typed in BBC.h. It is currently typed as heapptr *esr;
, which implies double indirection, so that *esr
points to the basic stack. I think it should be typed void *esr;
or char *esr;
since the value of esr
is the pointer to the BASIC stack.
Your code
PROCtest
copies down the contents of the BASIC stack, but as far as I can see it does nothing to actually move the stack pointer to point to the new stack location.
"As far as you can see" is not far enough, because it works! The stack pointer is indeed moved to the new location, as can easily be demonstrated:
PRINT "On entry:"
PRINT "HIMEM = &"; STR$~HIMEM
DIM s%% LOCAL -1
PRINT "Stack pointer = &"; ~s%%
PROCtest
PRINT "On exit:"
PRINT "HIMEM = &"; STR$~HIMEM
DIM s%% LOCAL -1
PRINT "Stack pointer = &"; ~s%%
END
DEF PROCtest
LOCAL b%%, d%%, s%%
DIM s%% LOCAL TRUE, b%% LOCAL 10000, d%% LOCAL TRUE
WHILE s%% < HIMEM
?d%% = ?s%%
d%% += 1 : s%% += 1
ENDWHILE
HIMEM = d%%
ENDPROC
I have tried doing this from within C code.
Don't (you've already been caught by trying to manipulate the stack pointer external to the interpreter, which you are not supposed to do)! Do it in BASIC code in PROC_initsockets and then pass the new value of HIMEM into your C code using SYS, then everything will work correctly.
As a side issue, I think the C variable
esp
is confusingly / incorrectly typed in BBC.h.
Sorry if it's confusing, but it's correct. Declaring it as void would break the pointer arithmetic, because sizeof(void) is 1 (in GCC and Clang) whereas sizeof(heapptr) is 4, which is what we want. Doing it this way ensures that the stack pointer is always correctly aligned, whereas declaring it as void would make it possible to adjust the stack pointer by non-multiples of 4 bytes.
I can only assume that it works because the stack space reserved by b%% LOCAL 10000
is not freed when the routine exits, even though it is defined as LOCAL.
In that case, how do I free that space in PROC_exitsockets
and move the stack pointer back up?
In that case, how do I free that space in
PROC_exitsockets
and move the stack pointer back up?
This is one way (it's possible there is a better way, but I haven't given it much thought). In an ideal world one would probably want to pass the 'amount to reserve' or 'amount to free' as parameters rather than as embedded constants (10000) as here:
PRINT "On entry:"
PRINT "HIMEM = &"; STR$~HIMEM
DIM s%% LOCAL -1
PRINT "Stack pointer = &"; ~s%%
PROCtest
PRINT "After PROCtest:"
PRINT "HIMEM = &"; STR$~HIMEM
DIM s%% LOCAL -1
PRINT "Stack pointer = &"; ~s%%
PROCfree
PRINT "After PROCfree:"
PRINT "HIMEM = &"; STR$~HIMEM
DIM s%% LOCAL -1
PRINT "Stack pointer = &"; ~s%%
END
DEF PROCtest
LOCAL b%%, d%%, s%%
DIM s%% LOCAL TRUE, b%% LOCAL 10000, d%% LOCAL TRUE
WHILE s%% < HIMEM
?d%% = ?s%%
d%% += 1 : s%% += 1
ENDWHILE
HIMEM = d%%
ENDPROC
DEF PROCfree
LOCAL b%%, d%%, s%%
DIM s%% LOCAL TRUE, b%% LOCAL 7, d%% LOCAL TRUE
b%% = 10000 + s%% - d%% - 4
s%%!-12 += b%% : d%% = s%% + b%%
WHILE s%% < HIMEM
?d%% = ?s%%
d%% += 1 : s%% += 1
ENDWHILE
HIMEM = d%%
ENDPROC
Thanks for that. My version is:
1 WHILE TRUE
2 INPUT "Space to allocate", space%
3 PRINT "Space requested = &";~space%
4 PRINT "On entry:"
5 PRINT "HIMEM = &"; STR$~HIMEM
6 DIM s%% LOCAL -1
7 PRINT "Stack pointer = &"; ~s%%
8 PROC__socklib_rsrv(space%)
9 PRINT "After PROC__socklib_rsrv:"
10 PRINT "HIMEM = &"; STR$~HIMEM
11 DIM s%% LOCAL -1
12 PRINT "Stack pointer = &"; ~s%%
13 PROC__socklib_free(space%)
14 PRINT "After PROC__socklib_free:"
15 PRINT "HIMEM = &"; STR$~HIMEM
16 DIM s%% LOCAL -1
17 PRINT "Stack pointer = &"; ~s%%
18 ENDWHILE
19 END
20
21 REM Move HIMEM and BASIC stack down to make room for network heap
22 DEF PROC__socklib_rsrv(size%)
23 LOCAL b%%, d%%, s%%
24 DIM s%% LOCAL TRUE, b%% LOCAL size%-&20, d%% LOCAL TRUE
25 WHILE s%% < HIMEM
26 ?d%% = ?s%%
27 d%% += 1 : s%% += 1
28 ENDWHILE
29 HIMEM = d%%
30 ENDPROC
31
32 REM Move HIMEM and BASIC stack up recovering space used by network heap
33 DEF PROC__socklib_free(size%)
34 LOCAL b%%, d%%, s%%
35 DIM s%% LOCAL TRUE, b%% LOCAL 7, d%% LOCAL TRUE
36 b%% = size% + s%% - d%% - &20
37 s%%!-12 += b%% : d%% = s%% + b%%
38 WHILE s%% < HIMEM
39 ?d%% = ?s%%
40 d%% += 1 : s%% += 1
41 ENDWHILE
42 HIMEM = d%%
43 ENDPROC
And test results:
Space to allocate? 8192
Space requested = &2000
On entry:
HIMEM = &2002A800
Stack pointer = &2002A7F8
After PROC__socklib_rsrv:
HIMEM = &20028800
Stack pointer = &200287F8
After PROC__socklib_free:
HIMEM = &2002A800
Stack pointer = &2002A7F8
Space to allocate? 4096
Space requested = &1000
On entry:
HIMEM = &2002A800
Stack pointer = &2002A7F8
After PROC__socklib_rsrv:
HIMEM = &20029800
Stack pointer = &200297F8
After PROC__socklib_free:
HIMEM = &2002A800
Stack pointer = &2002A7F8
Space to allocate? 2048
Space requested = &800
On entry:
HIMEM = &2002A800
Stack pointer = &2002A7F8
After PROC__socklib_rsrv:
HIMEM = &2002A000
Stack pointer = &20029FF8
After PROC__socklib_free:
HIMEM = &2002A800
Stack pointer = &2002A7F8
Space to allocate? 1024
Space requested = &400
On entry:
HIMEM = &2002A800
Stack pointer = &2002A7F8
After PROC__socklib_rsrv:
HIMEM = &2002A400
Stack pointer = &2002A3F8
After PROC__socklib_free:
HIMEM = &2002A800
Stack pointer = &2002A7F8
Space to allocate? 512
Space requested = &200
On entry:
HIMEM = &2002A800
Stack pointer = &2002A7F8
After PROC__socklib_rsrv:
HIMEM = &2002A600
Stack pointer = &2002A5F8
R0 = 00000043 R8 = FFFFFFFF
R1 = 00000000 R9 = 00000000
R2 = 00000000 R10 = 2000AB7F
R3 = 00000000 R11 = 2002A560
R4 = 2002A788 R12 = 200413D8
R5 = 00000043 SP = 200415F8
R6 = 00000001 LR = 100110E1
R7 = 00000001 PC = 1000EEF4
SG = 2002A640 PSP = 61000000
Hard fault at line 39
So clearly a minimum that can be reclaimed.
So clearly a minimum that can be reclaimed.
It's the old memcpy vs memmove issue of the source and destination regions overlapping. It's highly unlikely to bite you, since you'll be freeing around 40 Kbytes, but if you really care you could call memmove
rather than using the BASIC copy loop:
32 REM Move HIMEM and BASIC stack up recovering space used by network heap
33 DEF PROC__socklib_free(size%)
34 LOCAL b%%, d%%, s%%
35 DIM s%% LOCAL TRUE, b%% LOCAL 7, d%% LOCAL TRUE
36 b%% = size% + s%% - d%% - &20
37 s%%!-12 += b%% : d%% = s%% + b%%
38 SYS "memmove", d%%, s%%, HIMEM - s%%
39 HIMEM = d%% + HIMEM - s%%
40 ENDPROC
Incidentally, it looks like you are trying to tweak the amount by which HIMEM is lowered/raised to be exactly equal to the passed parameter, by means of the - &20 adjustment in both PROC__socklib_rsrv()
and PROC__socklib_free()
. Just a heads-up that this won't work in general because the required adjustment varies between 32-bit and 64-bit versions, and between assembler and C versions, of the interpreter.
It's safer to accept that HIMEM will be moved a little more than the parameter you pass.
Incidentally, it looks like you are trying to tweak the amount by which HIMEM is lowered/raised to be exactly equal to the passed parameter, by means of the - &20 adjustment in both
PROC__socklib_rsrv()
andPROC__socklib_free()
. Just a heads-up that this won't work in general because the required adjustment varies between 32-bit and 64-bit versions, and between assembler and C versions, of the interpreter.It's safer to accept that HIMEM will be moved a little more than the parameter you pass.
Since this code is only used in the Pico version of socklib I don't think that matters. The constant in the free
routine had to be adjusted anyway, since, once I added the size%
parameter to your routines, the amount of memory freed did not match that allocated.
I have now pushed my latest code to the repository. For now it still defaults to the original static allocation method. I will change this after further testing.
Should you wish to test, then git clone
or git pull
the latest version of my repository, then in folder PicoBB/console/pico_w run the command make NET_HEAP=1
.
Since this code is only used in the Pico version of socklib I don't think that matters.
That's probably true, but I was thinking about publishing the routines at the BBC BASIC Wiki, since they are potentially of much wider application, and then they would need to be 'universal'.
Presumably if you are only allocating the memory in PROC_initsockets and freeing it in PROC_exitsockets you don't really need separate routines anyway, the necessary code could be included in-line, then nobody would be tempted to use it elsewhere.
The constant in the
free
routine had to be adjusted anyway, since, once I added thesize%
parameter to your routines, the amount of memory freed did not match that allocated.
The routines, as I supplied them, didn't always match anyway - they sometimes freed 4 bytes less than they allocated. This was deliberate, again for compatibility across the multiple interpreters; you'd have to call them a large number of times for it to matter.
Presumably if you are only allocating the memory in PROC_initsockets and freeing it in PROC_exitsockets you don't really need separate routines anyway, the necessary code could be included in-line, then nobody would be tempted to use it elsewhere.
I thought about that, but since I don't really understand exactly how these routines work I was not sure what the effect of other variables (or code) would have on the allocation. And if people read the library source to discover these routines then they could also find and copy any inline source.
The routines, as I supplied them, didn't always match anyway - they sometimes freed 4 bytes less than they allocated. This was deliberate, again for compatibility across the multiple interpreters; you'd have to call them a large number of times for it to matter.
Providing you are confident that the free routine always releases less than (or equal to) that allocated. Freeing more than allocated would result in eating into the bottom of any libraries.
Providing you are confident that the free routine always releases less than (or equal to) that allocated. Freeing more than allocated would result in eating into the bottom of any libraries.
That's exactly my point. If you run your versions of the routines in BBC BASIC for Windows (coded in assembler) it will indeed try to free four bytes more than it allocates! Hence the -4 in my version which you removed.
The consequences could be even worse than corrupting libraries, for example BBC BASIC (z80) v5 doesn't support libraries and there's no guarantee there will be any memory at all above HIMEM!
That's exactly my point. If you run your versions of the routines in BBC BASIC for Windows (coded in assembler) it will indeed try to free four bytes more than it allocates! Hence the -4 in my version which you removed.
So completely generic routines should probably identify the platform they are running on, and adjust behaviour appropriately.
So completely generic routines should probably identify the platform they are running on, and adjust behaviour appropriately.
I disagree, but I'm too tired to argue the point.
Should you wish to test, then
git clone
orgit pull
the latest version of my repository, then in folder PicoBB/console/pico_w run the commandmake NET_HEAP=1
.
Having just got around to trying that, I'm falling at the first hurdle because my build process expects there to be a lib
subdirectory, which there is in PicoBB/console/pico but there isn't in PicoBB/console/pico_w.
Making sure that the correct libraries are included with my build has always been an issue, and copying them into the lib
subdirectory has been the way I do it. I'm not sure what to do if there isn't one!
See the folder PicoBB/console/pico_w/examples/lib
.
That includes the Pico version of socklib
.
See the folder
PicoBB/console/pico_w/examples/lib
.
Oh. It seems strange (and confusing) that the directory structure is different for the WiFi version compared with the non-WiFi version. In addition, the two files that are in PicoBB/console/pico/lib/
are not in PicoBB/console/pico_w/examples/lib/
!
Do we really need two versions any more, now you've managed to reduce the memory penalty if socklib
isn't used?
Oh. It seems strange (and confusing) that the directory structure is different for the WiFi version compared with the non-WiFi version.
Yes, I agree it is slightly inconsistent. I will probably fix at some point. I think examples/lib
is slightly more logical as:
In addition, the two files that are in PicoBB/console/pico/lib/ are not in PicoBB/console/pico_w/examples/lib/!
I don't duplicate files in multiple source folders. Instead PicoBB/src/lfsutil/pico_examples.py
assembles the complete filesystem in build/tree
from multiple source folders under the control of PicoBB/pico_examples.cfg
.
Do we really need two versions any more, now you've managed to reduce the memory penalty if socklib isn't used?
Perhaps not. It will still be possible to produce builds without network support by specifying the appropriate options. I may remove the Non-W folders when I produce a build for the Pico 2. I have some trial code, but currently no hardware to test it on.
I agree it is slightly inconsistent. I will probably fix at some point. I think examples/lib is slightly more logical
I think this probably arose because BBCSDL (the GUI version) has a huge number of examples (and associated resource files), more than 800 in all! So they're organised in BBCSDL/examples
which sits alongside BBCSDL/lib
. The console editions, by contrast, only have about 20 example files so it didn't seem worth the effort of putting them in a subdirectory where a user might not even notice them.
I don't duplicate files in multiple source folders. Instead
PicoBB/src/lfsutil/pico_examples.py
assembles the complete filesystem inbuild/tree
from multiple source folders under the control ofPicoBB/pico_examples.cfg
.
Ah, so you're saying I could copy library files into either PicoBB/console/pico/lib
or PicoBB/console/pico_w/examples/lib
and the end result will be the same? In that case I should probably put them where I have before.
Ah, so you're saying I could copy library files into either
PicoBB/console/pico/lib
orPicoBB/console/pico_w/examples/lib
and the end result will be the same? In that case I should probably put them where I have before.
Providing you copy the files before make
, and do the build in console/pico_w
then yes. If you do the build in console/pico
, then files in console/pico_w/examples/lib
will not be copied.
Next problem (sorry):
CMake Error at symbols/CMakeLists.txt:11 (if):
if given arguments:
"STREQUAL" "rp2350"
Unknown arguments specified
Do I perhaps need to update the Pico SDK and/or my other tools (I'm running quite an old PiOS)?
I am using v2.0 of the SDK. In fact, owing to an issue with some of the static_assert
statements in v2.0 of the SDK I am using the develop
branch rather than the default master
branch of the SDK.
I have just pushed a small change to my CMakeLists.txt
file which will hopefully allow compilation with v1.x of the SDK.
I am still using the older Bulseye version of RPi OS, not the latest Bookworm.
I have just pushed a small change to my
CMakeLists.txt
file which will hopefully allow compilation with v1.x of the SDK.
Unfortunately there still seems to be an incompatibility with the version of the SDK I have:
*** ERROR: Header file /home/pi/pico/pico-sdk/src/common/pico_base_headers/include/pico/config.h not found
(there's no pico_base_headers
directory in common
).
If I were to update the SDK (using pico_setup.sh
) would that cause other problems?
I forgot that v2.0 of the SDK moved the location of a few of the header files.
As I mentioned, the default master
branch of v2.0 of the SDK has issues with static_assert
if building with older versions of GCC, see here and here. I can't remember whether this affects building for Pico_W or just Pico_2.
The moved header files only cause a problem because I parse them to get the function names to include for SYS. Let me think whether I can work around this.
OK, I have pushed a change which makes the location of some header files depend upon SDK version.
Tested with SDK v2.0.
Hopefully I have also correctly recovered the paths for SDK v1.x, but not tested.
OK, I have pushed a change which makes the location of some header files depend upon SDK version.
That's an improvement, and it now proceeds much further, but I'm eventually getting this error:
cc1: fatal error: pico/platform/compiler.h: No such file or directory
As that's a relative directory I'm not too sure where it's looking. I've confirmed that an earlier version of PicoBB still builds successfully so I don't think my toolchain is broken.
That was caused by a work-around for another SDK v2.0 bug.
My latest push makes the work-around SDK version specific.
My latest push makes the work-around SDK version specific.
Nearly there. Now it builds right up to the very last step, when uf2merge fails because filesystem_pwc.uf2
is empty:
ls -al *.uf2
-rw-r--r-- 1 pi pi 1217024 Oct 15 18:24 bbcbasic_pwc.uf2
-rw-r--r-- 1 pi pi 0 Oct 15 18:23 filesystem_pwc.uf2
Looks like the reason is that it runs out of space in the filesystem (I have copied extra files into /lib
and /examples
):
../../littlefs/lfs.c:691:error: No more free space 0xdd
although this doesn't abort the build and I had to search a long way back to find it.
Is it easy to tell by how much it overflowed, so I can try to find something to delete? Everything fitted with the non-w build.
As per topic on Raspberry Pi forums, Richard Russel's build of PicoBB is missing network support routines.
However my build has them:
My build sequence is simply:
We need to identify what is different about the way Richard builds the program.