Merge branch 'master' of github.com:mmueller41/genode

This commit is contained in:
Michael Mueller
2022-10-14 14:31:37 +02:00
1561 changed files with 27337 additions and 15653 deletions

View File

@@ -1 +1 @@
22.05 22.08

View File

@@ -4,6 +4,67 @@
=========== ===========
Sculpt OS release 22.10 | 2022-10-13
####################################
| Sculpt OS 22.10 is a maintenance release of our Genode-based
| general-purpose OS. It imposes a new rigid regime to the management of
| low-level devices, improves USB hotplug support, and comes with numerous
| performance optimizations.
The just released version 22.10 of the Sculpt operating system bears the fruit
of our year-long effort to apply the rigidity of Genode's architecture to the
management of PCI configuration and device interrupts. This sweeping change
left no single device driver unturned. If we did our job right, you should not
notice any visible difference from the previous Sculpt version.
However, you should definitely _feel_ a difference when using the new version.
We put several performance optimizations in place - from accelerated system
startup, over increased network thoughput, to improved user-interface
responsiveness. Moreover, we put much emphasis on stressing Sculpt's USB
hotplug support, which includes the dynamic assignment and revocation of
USB devices to and from virtual machines.
With respect to available software, Sculpt users can enjoy an updated
Chromium engine - via the Falkon or Morph web browsers - and an updated
audio driver based on OpenBSD 7.1.
Sculpt OS 22.10 is available as ready-to-use system image at the
[https://genode.org/download/sculpt - Sculpt download page] along with
updated [https://genode.org/documentation/articles/sculpt-22-10 - documentation].
Genode OS Framework release 22.08 | 2022-08-31
##############################################
| The overarching theme of Genode 22.08 is the emerging phone variant of
| Sculpt OS, touching topics as diverse as USB ECM, Mali-400 GPU, SD-card
| access, telephony, mobile-data connectivity, the Morph web browser, and a
| custom user interface. Among the further highlights are new tracing tools,
| improved network performance USB smart-card support, and VirtIO drivers for
| RISC-V.
The vision of a Genode-based smart phone is certainly our most ambitious
undertaking since we created Sculpt OS for the PC. Over the past two years, we
relentlessly pursued this vision while targeting the PinePhone hardware.
The scope of work reaches from custom firmware for the system-control
processor, over kernel development, a staggering variety of device drivers, to
the user-interface and application level. With Genode 22.08, those efforts
culminate in a first complete system - a phone variant of Sculpt OS. The
[https://genode.org/documentation/release-notes/22.08 - release documentation]
tells the story behind this line of work in great detail.
Beside phone-related topics, the new release features new tooling for
gathering and analyzing system traces that allow for holistic performance
studies covering the interplay between components. One particular success
story of the new trace recorder is a profoundly improved network performance.
Further highlights are the support for USB smart cards via PKCS#11, VirtIO
drivers for RISC-V, and the update of Qt5 to version 5.15.2.
For the complete picture, please enjoy the official
[https:/documentation/release-notes/22.08 - release documentation of version 22.08...]
Genode OS Framework release 22.05 | 2022-05-31 Genode OS Framework release 22.05 | 2022-05-31
############################################## ##############################################

866
doc/release_notes/22-08.txt Normal file
View File

@@ -0,0 +1,866 @@
===============================================
Release notes for the Genode OS Framework 22.08
===============================================
Genode Labs
The overarching topic of version 22.08 is the emerging phone version of the
Genode-based Sculpt OS, targeting the PinePhone. The immense breadth and depth
of this line of work presented in Section [Genode coming to the phone]
touches topics as diverse as telephony, mobile-data connectivity, a custom
user interface, a mobile web browser, the GPU, SD-card access, USB, and audio
control.
With the growing sophistication of Genode-based systems, performance
optimizations come more and more into focus. Aided by the new tools introduced
in Section [Enhanced tooling for system tracing], we were able to profoundly
improve the network performance of Genode's user-level network routing
component. Speaking of optimizations, the current release reduces the CPU
overhead of our Linux device-driver environment
(Section [Linux-device-driver environment (DDE Linux)]) and
improves the responsiveness of GUIs based on Genode's menu-view component
(Section [Menu-view performance]).
Further topics of the new version reach from our forthcoming platform-driver
consolidation across PC and ARM-based devices, over the use of USB smart
cards, to new VirtIO drivers on RISC-V.
Genode coming to the phone
##########################
Our [https://genode.org/about/road-map - road map] for this year states the
goal of reaching a useful base line of functionality of Genode on the
PinePhone. This entails the principle ability to use the device as a phone -
receiving and issuing voice calls - and a mobile internet browser. Once
reached, this base line of functionality will allow us to routinely use Genode
on the device ("eating our own dog food"), experience pain points, guide
optimization efforts towards user-visible areas that matter, and faithfully
evaluate non-functional aspects like battery lifetime with real-world work
loads under realistic conditions.
For the Genode-based phone, we pursue the combination of a minimally-complex
trustworthy base system with a generally untrusted Web browser as application
runtime. The feature set of the base system corresponds to the bare-bones
[https://genode.org/download/sculpt - Sculpt OS] extended with appliance-like
feature-phone functionality. Thanks to Sculpt's rigid component-based
structure and the overall low complexity, it promises high reliability and
security. The application runtime is hosted on top of the base system without
tainting the assurance of the base system. In contrast to the appliance-like
and rather static feature set of the base system, the application runtime
anticipates a great variety of modern-day application scenarios, universally
expected commodity user-interface paradigms, and fast-paced software updates.
E.g., we aspire the use of WebRTC-based video conferencing via Jitsi as one
reference scenario.
Since we succeeded in bringing the Chromium web engine - the base technology
of most modern web browsers - to life as a
[https://genodians.org/nfeske/2022-01-27-browser-odyssey - native Genode component],
users of Sculpt OS are able to use a fully featured web browser without
relying on virtualization. With the use case of the browser on a mobile phone
in sight, we already ensured that the browser would work on 64-bit ARM
hardware. However, whereas we could showcase the technical feasibility of
Chromium on Genode, the practical usability eventually depends on a suitable
mobile user experience, which was largely disregarded by the desktop-oriented
Falkon browser that we enabled on Genode.
Assessment
----------
Fortunately, we discovered the Morph web browser while experimenting with
[https://xnux.eu/p-boot-demo/ - various Linux distributions] on the PinePhone.
Among the various alternatives to Android, the Ubuntu Touch UI - alongside
Sailfish OS - stood out for its refined user experience, subjectively.
The unobtrusive Morph browser as used by default on Ubuntu Touch left a
particularly good impression on us. To our delight, we found that this
browser relies on Qt5 and the Chromium web engine as its foundation, both of
which we already had enabled on Genode. Out of this observation grew the idea
of reusing the Morph browser as application runtime on our Genode-based phone.
But we had to consider several risks.
First, would the heaviness of Chromium overwhelm the rather resource-constrained
PinePhone hardware when executed on Genode? In contrast to Linux, Genode's
POSIX environment is less sophisticated and - most importantly - does not
provide the over-provisioning of memory resources. The latter could be a show
stopper.
Second, the build mechanics of the browser deviate from the beaten track we
covered so far, specifically the use of QMake. The Morph browser
unconditionally depends on CMake as build tool. Even though we gathered
[https://genodians.org/nfeske/2019-11-25-goa - early experiences], with using
CMake for building Genode executables, we did not attempt using CMake for
complex Qt5 applications targeting Genode so far.
Finally, we discovered a so-called Ubuntu-Touch-UI toolkit as an
additional dependency over Qt5. It presumably extends Qt5's QML with
custom user-interface widgets for mobile user interfaces. In contrast
to the multi-platform Qt5 framework, Ubuntu Touch generally targets
Linux only, which raised a number of concerns with respect to hidden
assumptions on the underlying platform. For example, the expectation
of a certain service manager, the direct use of the Linux kernel interface,
or accidentally complex library dependencies.
Methodology
===========
As practiced during our work with bringing the Chromium-based Falkon web
browser to Genode, we took several intermediate steps to mitigate technical
risks as far as possible.
Pruning dependencies
--------------------
The first step was building the Morph browser from source for its regular
designated target platform, namely Linux. This step allowed us to validate the
functionality of the browser built from source as opposed to merely testing a
binary package. During this process, we learned about the mandatory dependence
on CMake as build tool. We also identified the following library dependencies
as sources of uncertainty.
*Ubuntu-UI toolkit* is a collection of QML widgets for smartphone apps.
It is built via QMake and comes with its own set of dependencies.
We were specifically concerned by QtSystemInfo, QtOrganizer, D-Bus, and
gettext. Genode has no meaningful equivalent to any of these dependencies.
The *Ubuntu Themes* dependency comprises graphical assets, used on Ubuntu
Touch. *Ubuntu-UI extras* extends Qt's feature set by functionality like the
'TabsBar' QML-Widget introduces additional transitive dependencies
such as the [https://www.cups.org/ - CUPS printing system] or
the [https://exiv2.org/ - Exiv2] image metadata library.
Further dependencies worth noting are QNetworkInterface, QtConcurrent, QtDBus,
QtSystemInfo, unity-action-api, and D-Bus. Those libraries do not exist in
Genode and may even raise conceptual problems. For example, the D-Bus
inter-component mechanism on Linux is not in line with Genode's
capability-based inter-component communication.
With the first manually built executable of Morph created on Linux, we could
repeatedly remove dependencies piece by piece and validate the functioning of
the browser after each step. We ultimately reached a point where most of the
library dependencies could be cut off while the core functionality of the
browser - the ability to view web pages - stayed intact. The resulting
minimized version of the Morph browser thereby served as starting point for
the subsequent porting work to Genode.
Re-targeting to Genode
----------------------
To stay as close as possible to the original browser, we decided to reuse the
browser's build system by tweaking the CMake build tool such that the project
could be cross compiled for Genode, similar to the approach we successfully
employed for QMake in the past. At first, we targeted Genode/Linux on x86,
which is still close to the browser's natural environment. Once the first
version of the browser came to life, we immediately cross-validated the result
on the 64-bit ARM architecture as this is our primary target. Subsequently, we
moved away from Linux by moving the browser over to NOVA (on Sculpt) on PC
hardware as well as our custom base-hw microkernel in order to target the
actual PinePhone.
[image touch_ui]
Ubuntu-Touch UI gallery demo running on Genode
The methodology mirrored in large parts the approach we took for the original
porting of the Chromium web engine, but it was a much smoother experience
given that all road blocks we encountered during our Chromium work are solved
problems by now. Image [touch_ui] shows the browser's underlying
user-interface tool kit in action, running directly on Genode. Image [morph]
shows the Morph browser hosted in Genode's window system.
[image morph]
Morph browser running on Genode
Unexpected caveats
==================
However, the smooth ride of re-targeting the browser to Genode ended once
we discovered the extremely poor interactive performance of the browser
running on Genode. This is in contrast to our prior experience with the
Chromium-based Falkon browser which achieved comparable performance to Linux.
The performance degradation originated from the Ubuntu-UI toolkit, which
has a hard dependency on OpenGL despite being built atop the Qt5 framework.
In several instances, the Ubuntu-UI toolkit accesses the OpenGL context
directly, which is handled by a software fallback implementation in the
Mesa library. We found the removal of those offending accesses infeasible
because this change would cause several widgets appearing incomplete.
To attain the visual completeness of the user interface, we also had to
enhance the Genode-specific back end of Qt (QPA). However, even though
we achieved correctly looking results, the performance of Mesa3D without
GPU acceleration made the user interface practically unusable, even on
powerful PC hardware, not speaking of the resource-constrained PinePhone.
We came to the conclusion that the Morph browser's hard dependency
on hardware-accelerated graphics cannot be worked around. This realization,
in turn, spawned the line of work reported in
Section [Hardware-accelerated graphics].
As another - but arguably much less dramatic - caveat, we found the touch user
interface behaving strangely in some situations when running on Genode. The
reason turned out to be a disparity of Genode's notion of touch-release events
from the expectations of Qt. Whereas Genode's input-event interface does not
report a positional value of a touch-release event, Qt expects a positional
value that corresponds to the original touch event. Fortunately, once this
disparity had been identified, we could easily emulate the expected behavior
locally in Genode's QPA plugin.
Hardware-accelerated graphics
=============================
As mentioned above, we were taken by surprise by the hard dependency of the
Morph browser on GPU-accelerated graphics. Even though we have explored the
principle use of a GPU on an ARM-based platform before, our prior line of work
was targeting the Vivante GPU of the NXP i.MX8 SoC, which is different from
the Mali-400 GPU as present in the PinePhone's A64 SoC. Originally, we did not
plan to deal with the PinePhone's GPU at the current stage. But the
requirement of the Morph browser abruptly changed our priorities.
As a rapid experiment, we took the challenge to port the Lima driver for the
Mali-400 GPU from Linux to Genode and combine it with the matching user-level
driver code of the Mesa library. Even though this experiment was pursued on
short notice and risky, it was at least a tangible straw. To our delight,
however, the first functional rapid prototype came to life after merely two
weeks of work, which is almost an order of magnitude faster than our past
efforts. The reason of this success is many-fold. First, our recently
established methodology and tooling for porting Linux device drivers - as
described in our comprehensive
[https://genode.org/documentation/genode-platforms-22-05.pdf - Porting Guide] -
streamlines the formerly labor-intensive grunt work. Second, we greatly
benefited from our practical experience with GPUs accumulated over the past
few years. And third, even though the Mali-400 is different from the Vivante
GPU, the integration into the Linux GPU stack follows very similar patterns,
unlike Intel GPUs. So we found our existing knowledge largely applicable.
[image glmark2]
GLMark2 reference application using the GPU
Following the initial rapid prototype, we successively refined this work to
the point where the GPU driver became usable for the Morph browser on the
PinePhone. Thanks to the added driver, the interactive performance got boosted
to an acceptable level.
Mobile data connectivity
========================
It goes without saying that a web browser requires network connectivity,
which is a topic we had left unaddressed on the PinePhone until now.
However, given our
[https://genode.org/documentation/release-notes/22.05#Telephony - recent line]
of modem-related work in the context of telephony, we foresaw a low-complexity
solution to attain mobile data connectivity.
Today's LTE modems offer
[https://genodians.org/ssumpf/2020-12-04-mbim - QMI or MBIM] protocol support
in order to configure and handle mobile data connections. Both protocols are
in binary format and require a separate USB device (called Wireless Mobile
Communication Device). For Genode, this would mean to add support for this
device to USB while additionally the QMI or MBIM library would have to be
ported and adjusted to Genode. For the
[https://www.quectel.com/product/lte-eg25-g - Quectel EG25 modem]
in the PinePhone, we found a much simpler solution to handle mobile data
connections. The modem can be configured to emulate a USB Ethernet device
([https://en.wikipedia.org/wiki/Ethernet_over_USB - ECM device]).
In this operational mode, the modem will automatically connect to the carrier
and register itself as USB Ethernet device at the PinePhone's USB host
controller. Genode can thereby access the device through the USB networking
and CDC Ethernet drivers. The modem also offers a DHCP server and will hand
out a local IP address upon a DHCP request to Genode. Internally the modem
will use [https://en.wikipedia.org/wiki/Network_address_translation - NAT] in
order to translate IP requests from Genode to the address received from the
carrier.
As a prerequisite to conduct this solution, we had to enable a USB
host-controller driver for the PinePhone. Of course, we took advantage of our
modern DDE Linux porting approach for this work, which allowed to attain a
functional USB driver in merely two weeks. This driver must be combined with
our existing USB Ethernet driver (usb_net) that we swiftly extended to support
ECM based devices.
With this driver infrastructure in place, the USB network device of the modem
appears as uplink to Genode's NIC router. The NIC router, in turn,
successfully obtains a local IP address that is network-translated by the
modem. At the carrier side, IP network connectivity can be established by
issuing AT-protocol commands over UART. So the first prototype of the
low-level network connectivity worked as anticipated. With this practical way
of keeping the complexity of binary configuration protocols out of the loop,
we can maintain the low-complexity implementation of telephony and SIM
configuration via the UART control channel while regarding IP connectivity -
and the unavoidable complexity of USB - as an entirely complementary feature.
Phone flavor of Sculpt OS
=========================
Seeing the various puzzle pieces of the Morph browser scenario - GPU
acceleration, data connectivity, the browser itself - coming together, it was
time for the integration of those pieces into an overall system. The natural
basis of such a Genode-based system is
[https://genode.org/download/sculpt - Sculpt OS],
which complements Genode with universally expected operating-system features
such as interactive system configuration as well as the installation and
deployment of software packages.
Sculpt OS was originally designed for PC-based use cases. Its administrative
user interface is largely mouse and keyboard driven, and network connectivity
is usually attained by a wired or wireless LAN connection. Although we
presented a first version of
[https://fosdem.org/2022/schedule/event/nfeske/ - Sculpt OS on the PinePhone]
earlier this year, the call for a touch-oriented user interface is more than
obvious. Hence, we went forward with creating a phone-specific variant
of Sculpt. Similar to the original Sculpt OS, the system consists of two
largely isolated domains, the administrative domain called Leitzentrale and
the domain of user-installed components called desktop. The user can switch
between both domains at any time using a secure attention key or gesture.
On the phone, the Leitzentrale domain plays the role of a feature-phone
appliance that provides the most fundamental device functionality such
as the interaction with the SIM card, power control, telephony, network
configuration, storage management, and software installation. We approached
the concept of the user interface from a clean slate striving for simplicity.
[image sim_pin]
Emerging mobile-phone flavor of Sculpt OS
As the first use case, we addressed telephony, displaying incoming calls,
presenting the options for accepting/rejecting calls, and initiating calls
using a dial pad. By modelling these scenarios, we could validate the
user-interface concept of the evolving phone version of Sculpt's Leitzentrale.
User interaction with the SIM card
==================================
The administrative user interface mentioned above must be matched by the
underlying middleware that talks to the modem. Remember that our
[https://genode.org/documentation/release-notes/22.05#Telephony - original]
telephony scenario relied on the manual use of the modem's AT commands.
We ultimately have to control the modem's control channel by software using an
AT protocol stack. To bridge this gap with the lowest complexity possible, we
created a simple AT protocol implementation that is specifically designed for
Genode's state-driven component model.
The modem driver - hosting the AT protocol driver - accepts a configuration
that expresses the desired state (as opposed to desired actions). For example,
a configuration may look as simple as follows.
! <config speaker="yes" pin="1234">
! <call number="+49123123123"/>
! </config>
The AT protocol implementation takes this configuration and the current modem
state as the basis for determining a sequence of modem commands needed to
attain the desired state. For example, if the modem is not powered, the driver
steps through the powering sequence. Or in case the SIM PIN is required, the
driver supplies the corresponding command to supply the configured PIN.
To allow interactive usage, the driver supports dynamic reconfiguration.
E.g., to cancel the outbound call of the example above, the configuration
would be updated with the '<call>' node removed. Given this approach, an
interactive user interface comes down to generating such simple
configurations.
Vice versa, the driver exports the modem's state as a state report, which is
updated whenever the modem state changes. E.g., an incoming call is reflected
to the consumer of this state report with all information relevant for an
interactive user interface. For example, the state report entails the power
state, PIN state, and call states (incoming, outbound, alerting, rejected).
This design nicely hides the peculiarities of the AT protocol from Genode's
component interfaces.
At the current stage, with less than 1000 lines of code, the AT protocol
implementation suffices for basic telephony needs, supporting the interaction
with the SIM card, managing call states, initiating calls, and driving the
modem power up and down. It also takes care of establishing the modem
configuration needed for USB ECM networking.
Current state
=============
The current version of the phone variant of Sculpt OS is able to control the
power state of the modem, interact with the SIM card (PIN entry), initiate
phone calls via a dial pad, pick up inbound calls, establish mobile-data
network connectivity, and deploy a preconfigured application scenario.
The interactive switching between the base system and the application runtime
can be triggered at any time by touching the left border of the touch screen.
[image sculpt_pinephone]
The runtime graph of the base system (left) reveals the relationships of the
Morph browser with other components (right).
This flavor of Sculpt OS evolves in the
[https://github.com/nfeske/genode-allwinner - genode-allwinner] repository,
specifically within the _sculpt/_ and _src/app/phone_manager/_ directories.
The latter asserts the role of Sculpt's _gems/src/app/sculpt_manager_.
We invite seasoned developers - especially those who are following the
[https://genodians.org/nfeske/index - Pine-fun article series] - to experiment
with the new phone variant. It can be built via the following command:
! built/arm_v8a$ make run/sculpt KERNEL=hw BOARD=pinephone SCULPT=phone
For a broader audience, we plan to provide a ready-to-use SD-card image for
the PinePhone in tandem with the next release of Sculpt OS.
Enhanced tooling for system tracing
###################################
Since release 13.08, Genode features a
[https://genode.org/documentation/release-notes/13.08#Light-weight_event_tracing - light-weight event-tracing facility]
that comes in form of core's TRACE service. Up to now, it has merely been used
for capturing textual trace messages. The two prominent monitor components are
the
[https://genode.org/documentation/release-notes/18.02#New_trace-logging_component - trace_logger]
and the
[https://genode.org/documentation/release-notes/19.08#Tracing - VFS plugin]
The trace recorder is a new monitor component that is designed for binary trace
formats. Currently, it supports the Common Trace Format (CTF) and pcapng.
CTF is a compact and scalable format for storing event traces. It is supported
by [https://www.eclipse.org/tracecompass/ - TraceCompass], an Eclipse-based
tool for trace analysis and visualization. Pcapng is a packet-capture format
used by Wireshark.
In order to support capturing network packets, we added a 'trace_eth_packet()'
method to Genode's trace-policy API and equipped the NIC router with a
'trace_packets' option to control packet capturing on domain level. For manual
instrumentation of components, we also added a 'checkpoint()' method to the
trace-policy API.
For more details, please refer to the following Genodians article.
:Identifying network-throughput bottlenecks with trace recording:
[https://genodians.org/jschlatow/2022-08-29-trace-recorder]
Base framework and OS-level infrastructure
##########################################
Networking optimizations
========================
With the new trace recorder at hand, we took an effort in optimizing Genode's
network throughput. First, we implemented a benchmark component called
"nic_perf" that sends and/or receives an infinite stream of UDP packets in
order to stimulate the involved networking components in separation. As a
consequence of its central role, we particularly focused on the NIC router as
a starting point.
As a base line, we took two 'nic_perf' components: one as a sender and the other
as a receiver. By taking any copying or packet inspection out of the loop, we
could verify that the packet-stream interface holds up to our expectations with
respect to attainable throughput. However, as soon as we put a NIC router in
between, the throughput dropped to approx. 10% of our base line. On older
ThinkPads, this meant sub-gigabit throughput and on a Cortex-A9 @ 666MHz we
barely jumped over the 100Mbit mark.
Since we were not able to explain the substantial decrease in packet throughput,
we investigated with the help of the trace recorder and 'GENODE_LOG_TSC'.
As it turned out, the NIC router spent most of its time with exception handling
during routing-rule lookup, which is done for every packet. Since there are
multiple types of rules, a lookup takes place for every rule type. If no rule
was found for particular type, an exception was thrown and caught, which
turned out to be incredibly expensive. We therefore eliminated exceptions from
common-case code paths, more precisely from rule lookup, from ARP-cache
lookup, and from packet allocation. The result impressed us with a tripled
throughput.
Another bottleneck that we identified were frequent 'trigger_once' and
'elapsed_ms' RPCs. Given that the NIC router only maintains very
coarse-grained timeouts, such frequent RPCs to the timer seemed finical.
Sparing the details, we were able to significantly reduce the number of
these RPCs by relaxing the precision of the NIC router's time keeping.
Along the way, we identified a few more, minor, tweaks:
* We increased the default value of 'max_packets_per_signal' from 32 to 150.
This value determines the maximum number of packets that are consumed from an
interface at once.
* We eliminated eager packet-stream signalling from the NIC router to improve
batch processing of packets. With this change, packet-stream signals are only
emitted once the NIC router processed all available or
'max_packets_per_signal' packets.
* We implemented incremental checksum update for UDP/TCP according to RFC1071.
* We discovered and fixed a few corner cases in the packet-stream interface
with respect to the signalling.
* We fixed allocation errors in the 'ipxe_nic_drv' that popped up during high
TX load.
In the end, we attained a ~5x speed up (exact values depending on the hardware)
for the NIC router.
Event-filter improvements for touch devices
===========================================
The phone variant of Sculpt OS calls for a way to trigger certain low-level
buttons or keys using the touch screen. In particular, the switch between the
administrative user interface and the runtime system must be possible at any
time. On the [https://genode.org/download/sculpt - PC version], this switch
is triggered by pressing F12, which is remapped to KEY_DASHBOARD. Even though
a physical button could be used on the phone in principle, there are three
arguments in favor of a virtual key. First, there are only three physical
buttons available (volume +/- and power) on the PinePhone. Remapping one of
those buttons to KEY_DASHBOARD deprives the button of its original purpose.
Second, the force needed for pressing a physical button may impede the
ergonomics of the device depending on how often the switch is needed. And
third, the physical buttons require a driver. When enabling a new device, this
barrier can be nicely sidestepped by a virtual key.
Given this rationale, we extended Genode's event-filter component with a new
'<touch-key>' filter type. Once added to the filter chain, it triggers an
artificial key tap (a press event followed by a release event) whenever the
user touches a preconfigured area on the touch screen. The filter node can
host any number of '<tap>' sub nodes. Each sub node must define a rectangular
area - using the attributes 'xpos', 'ypos', 'width', and 'height' - and the
name of the tapped key as 'key' attribute.
! <touch-key>
! <tap xpos="0" ypos="400" width="25" height="600" key="KEY_DASHBOARD"/>
! ...
! </touch-key>
The example above repurposes the 25 left-most pixels of the touch screen as
dashboard key. When touched, a pair of press and release events is fired at
once.
Menu-view performance
=====================
The administrative user interface of Sculpt OS is based on Genode's custom
menu-view component, which renders and updates graphical dialogs based on
high-level XML descriptions. Up to now, the component operated on Genode's
GUI-session interface with alpha-channel support. However, the alpha channel
noticeably impedes the software-rendering performance on lower-end devices
like the PinePhone. In the latter case, we'd prefer to trade the nice-looking
alpha blending for a better UI responsiveness.
We have now enhanced the menu-view component with two new optional
configuration attributes 'opaque' and 'background'. Setting 'opaque' to "yes"
suppresses the use of the alpha channel at the GUI session. This improves the
drawing performance by 20% on the PinePhone. The 'background' attribute can be
specified to define the reset color of the GUI buffer. It alleviates the need
to create a frame widget for the top level, significantly reducing the costs
for drawing the background pixels.
Finally, we found that the use of GCC's optimization level -O3 instead of the
default level -O2 increases the drawing performance on the PinePhone by 30%.
Combined, those optimizations result in an acceptable user experience of
Sculpt's administrative user interface on the PinePhone.
Device drivers
##############
USB networking via Ethernet control model (ECM)
===============================================
To implement mobile data connectivity on the PinePhone
(Section [Mobile data connectivity]), we added USB host-controller support
(EHCI) for the Allwinner A64 SoC to Genode by porting the corresponding
host-controller driver from Linux using our DDE approach. Since our existing
USB-over-Ethernet
[https://github.com/genodelabs/genode/tree/master/repos/dde_linux/src/drivers/usb_net - driver]
on Genode lacked support for the Ethernet Control Model, which is provided by
the modem, we added support for ECM as well.
GPU and Mesa driver for Mali-400
================================
As mentioned in Section [Genode coming to the phone], we enabled the principle
ability to use the Mali-400 GPU of the PinePhone under Genode. This support
entails two parts. The first part is the low-level driver code called Lima
that normally resides in the Linux kernel. This component provides a GPU
session interface. We transplanted the driver code to a dedicated Genode
component, which is hosted at the
[https://github.com/genodelabs/genode-allwinner - genode-allwinner] repository.
The second part is the user-level Mesa3D driver stack - hosted at the libports
repository - that is linked local to the application and uses the GPU session
to access the GPU.
The combination of both parts was successfully tested on the PinePhone and
the Pine-A64-LTS V1.2 board. Given that the primary motivation for this
line of work was our ambition to run the Morph web browser, we disregarded the
multiplexing of the GPU for now. The GPU driver currently supports only one
client at a time.
SD-card driver for the PinePhone
================================
In anticipation of running Sculpt OS on the PinePhone, we ported the Linux
SD/MMC-card driver to Genode. The driver - hosted at the
[https://github.com/genodelabs/genode-allwinner - genode-allwinner] repository -
was successfully tested with the PinePhone and Pine-A64LTS V1.2 board. For the
moment, only SD cards (no eMMC) are supported.
The provided _a64_sd_card_drv.run_ script illustrates the integration and use
of the driver.
Linux-device-driver environment (DDE Linux)
===========================================
Tickless idle operation
-----------------------
The DDE-Linux emulation library and thereby all ported drivers now support
the NO_HZ_IDLE Linux kernel configuration option, which disables periodic
timer ticks when ported drivers are idle. With this option, energy and up to
3% CPU time per driver can be preserved, which becomes significant especially
if multiple ported drivers are in use in sophisticated scenarios like Sculpt
OS.
Consistent use of SMP configuration
-----------------------------------
All kernel threads in the Linux device driver ports are currently mapped to one
and the same native Genode thread, using cooperative scheduling within the
emulation environment. Intuitively, it does not make much sense to address
multi-processing support provided by the original Linux kernel code.
Nonetheless, the drivers that we ported are normally used in the context of
SMP-aware Linux kernel configurations only. To not leave the well tested and
beaten track, we decided to switch on SMP support in all kernel configurations
we use as porting base.
This especially applies to the Linux drivers within the _repos/pc_
sub-directory, and the WireGuard port. Other driver ports already used SMP
support in their configuration.
As a side effect, we removed the insufficient emulation of so called "softirqs"
formerly used by the non-SMP driver ports, and replaced them with the original
implementation.
Forthcoming platform-driver modernization
=========================================
During the past year, we switched from board-specific platform driver APIs
step-by-step to one generic interface. But PC-related drivers still depend on
the legacy x86-specific platform driver and API, especially to the PCI-related
part of it.
To finalize the unification and modernization of the platform driver and its
API, there were still some pieces missing, which we added with the current
release.
While trying to switch PC-related Linux device driver ports to the new API, we
recognized that some drivers depend on additional information of the PCI
configuration space that were not exported so far. Namely, the fields for
sub-vendor, sub-product, and revision IDs were needed. Moreover, some ported
drivers use hard-coded indices of PCI base-address registers (BAR) to refer to
I/O resources of the device.
Therefore, we extended the pci_decode tool to export this additional
information, and to annotate I/O port ranges and memory attributes with the
corresponding BAR index. The generic platform driver parses this additional
information from a given devices ROM, and exports it to the corresponding
clients accordingly. The correlation between I/O resources and BAR indices is
only unveiled to clients where the platform driver's policy states that
physical information has to be provided, like in this example:
! <config>
! <policy label="usb_drv -> " info="yes">
! <pci class="USB"/>
! </policy>
! ...
! </config>
UHCI-specific platform extensions
---------------------------------
Some device-specific registers are only present within the PCI configuration
space. For instance UHCI controllers in the PC architecture provide a special
legacy support register only accessible via the PCI configuration space. This
register is used to hand over the USB hardware from the BIOS to the operating
system.
We did not want to pollute the platform API with a lot of device specific
tweaks nor provide unlimited access to the PCI configuration space to a
driver. Therefore, we implement the hand-over of the UHCI PCI device in the
platform driver if available. Moreover, we handle the Intel-specific resume
register whenever a session to the corresponding UHCI controller is opened.
Intel GPU information from Host Bridge
--------------------------------------
Some information needed by Intel GPU and framebuffer drivers is derived from
the Intel Graphics and Controller HUB (GMCH) respectively its control
register. It is used to calculate the GPU's Global Translation Table (GTT),
and the stolen memory sizes. Again we do not want to give access to the whole
configuration space of this sensitive device to either the GPU or the
framebuffer driver. Instead, the platform driver now detects Intel PCI graphic
cards, and exports the information found within the GMCH control register to
the corresponding client as part of the platform session's devices ROM.
Transition of PC drivers
------------------------
Although there is everything in place now to switch the remaining PC-drivers
to the generic platform driver and its API, we decided to do this step after
the current release. This way, we have time to stress-test the drivers during
our daily use of Genode, the remaining transitional work is planned for the
upcoming Sculpt OS release instead.
Libraries and applications
##########################
Qt5 and Morph browser
=====================
As mentioned in Section [Genode coming to the phone], we had to improve
Genode's Qt support to get the Morph browser to work. This work includes
added support for building Qt projects with CMake, the addition of missing Qt
modules like QtGraphicalEffects, and improving the OpenGL support of the QPA
plugin. The latter was needed for the Ubuntu UI Toolkit to display its widgets
correctly. Note that this change implies that QtQuick applications now use
OpenGL by default instead of the QtQuick software rendering fallback back end.
This can improve the experience when an accelerated GPU driver is available
but can also slow down a QtQuick application if only the Mesa software driver
('softpipe') is available on the target platform. In that case, it is possible
to enforce the use of the software QtQuick renderer by setting the following
environment variable in the configuration of the application:
! <env key="QT_QUICK_BACKEND" value="software"/>
When we tried to use the free public Jitsi server at [https://meet.jit.si] with
our ported web browsers, we noticed that our QtWebEngine Chromium version was
too old and caused issues like a non-working join button and failed WebRTC
connections. For this reason, we updated our Qt port to the latest version with
QtWebEngine support on FreeBSD, which at this time is version 5.15.2.
To use this new version, it is necessary to update the Qt5 host tools with the
'tool/tool_chain_qt5' script.
We also updated the Falkon web browser to the latest version 3.2.0.
Up-to-date Sculpt packages of both the Falkon and Morph browsers for x86_64 are
available in the 'cproc' depot.
USB smart cards via PKCS#11
===========================
With this release, Genode gains support for accessing USB smart-card devices
via PKCS#11. This is achieved through a port of the OpenSC PKCS#11 tool that is
now available as package for the Sculpt OS. A quick look into the features and
integration of the tool is possible using the new _pkcs11_tool_ run script
hosted in the [https://github.com/genodelabs/genode-world - genode-world]
repository. For a more detailed guide to the tool, you may read the
corresponding Genodians article.
:USB smart cards via PKCS#11:
[https://genodians.org/m-stein/2022-08-18-pkcs11-tool-1]
Sculpt OS improvements
======================
In addition to the major developments described in
Section [Genode coming to the phone], Sculpt OS has received several minor
refinements.
When integrating a
[https://genode.org/documentation/release-notes/22.02#Framework_for_special-purpose_Sculpt-based_operating_systems - Sculpt-based appliance]
with a predefined deploy configuration, the _sculpt.run_ script automatically
adds the required software packages as tar archive to the boot image. However,
for complex scenarios, it is sometimes desirable to keep the boot image small
and fetch the packages at runtime over the network. To support such use cases,
we added the new run-script argument 'DEPOT' with the possible values 'tar'
(default) and 'omit'. If the latter is specified, the deployed software
packages are excluded from the boot image and the run script merely prints the
versions of the required packages. This information can conveniently be used
as input for publishing the packages.
We added two new packages 'part_block' and 'ext2_fs' that simplify the access
of multiple block devices and partitions in manually curated deploy
configurations. The part_block package can be used in Sculpt's
_/config/deploy_ as follows.
! <start name="nvme-0.part_block" pkg="part_block">
! <route>
! <service name="Block">
! <parent label="nvme-0"/>
! </service>
! <service name="Report" label="partitions">
! <parent/>
! </service>
! </route>
! </start>
It can be combined with the 'ext2_fs' package to access the files stored on a
particular partition.
! <start name="nvme-0.4.fs" pkg="ext2_fs">
! <route>
! <service name="Block">
! <child name="nvme-0.part_block" label="4"/>
! </service>
! <service name="RM">
! <parent/>
! </service>
! </route>
! </start>
Platforms
#########
Qemu virtual platform
=====================
Because more and more architectures on Genode now support VirtIO drivers on
Qemu (ARMv7, ARMv8, and RISC-V), the generic board name "virt_qemu" did not
suffice for keeping a clean distinction between the separate architecture
requirements. Therefore, we decided to make the board name architecture
specific. The following board names are now supported on base-hw:
"virt_qemu_arm_v7a", "virt_qemu_arm_v8a", and "virt_qemu_riscv".
The "virt_qemu" board name was removed.
RISC-V
======
As suggested above Genode's RISC-V support got extended by VirtIO drivers.
This includes a block driver, a networking driver, keyboard and mouse handling
as well as basic framebuffer support. This way, it has become possible to test
interactive and networking scenarios on Genode's RISC-V version using Qemu.
This work was contributed by Piotr Tworek. Thanks a lot!
Allwinner A64
=============
In the
[https://genode.org/documentation/release-notes/22.05#Custom_system-control_processor__SCP__firmware - previous release],
we introduced our custom firmware for the PinePhone's system-control processor
(SCP). We have now generalized the firmware to cover also the Pine-A64-LTS
board. By establishing our custom SCP firmware as a base line for all A64-based
boards, we can make our A64 platform driver depend on the SCP for accessing the
PMIC (power management chip) instead of driving the RSB and PMIC by itself.
Build system and tools
######################
In this release, we improve support for booting Genode/Sculpt on UEFI
platforms in several aspects. First, the Bender tool gains a more robust
UEFI-boot detection mechanism while retrieving serial-device parameters. Also,
the GRUB boot loader was updated to version 2.06 and now keeps lower RAM
untouched from internal memory allocations, which prevents nasty surprises on
booting some UEFI devices. And last, our [https://ipxe.org/ - iPXE-based] boot
option received support for UEFI images when using the following run-tool
configuration.
! RUN_OPT += --include image/uefi
! RUN_OPT += --include load/ipxe

View File

@@ -21,6 +21,5 @@ content:
for spec in x86_32; do \ for spec in x86_32; do \
mv lib/mk/spec/$$spec/ld-fiasco.mk lib/mk/spec/$$spec/ld.mk; \ mv lib/mk/spec/$$spec/ld-fiasco.mk lib/mk/spec/$$spec/ld.mk; \
done; done;
sed -i "s/ld-fiasco/ld/" src/lib/ld/fiasco/target.mk
sed -i "s/fiasco_timer_drv/timer/" src/timer/fiasco/target.mk sed -i "s/fiasco_timer_drv/timer/" src/timer/fiasco/target.mk

View File

@@ -1 +1 @@
2022-05-24 1790ce242c001ed77aab9695f69923a44d1dc1d1 2022-10-11 1f0607de6493bad0e47b24e66d84474652e8b6be

View File

@@ -1,2 +0,0 @@
TARGET = ld-fiasco
LIBS = ld-fiasco

View File

@@ -1 +1 @@
2022-05-24 f7900083623a2009d35234c47d2475dea8f6cf53 2022-10-11 d258920f8664460c78eeea25fafb89eaa5e7adf5

View File

@@ -1 +1 @@
2022-05-24 f7d228f6419c2fc9b1b0faf4ba8d88862ba61e81 2022-10-11 1c94d29566bccccced246eeaf90702348e2b1a7f

View File

@@ -1 +1 @@
2022-05-24 391b798b7c1d1b44ff65d855980eb41a8f4a87c1 2022-10-11 2668fd23d5cbd45b8f632073fc7c155f96ecb848

View File

@@ -1 +1 @@
2022-05-24 79eab679e71dd70803b0e1647a23e2ba86c76f50 2022-10-11 8da054ff9e4c37895816fd30857b3c42d9e75eb0

View File

@@ -1 +1 @@
2022-05-24 7a16aeb081d1392c36d83f526936f17cc9560442 2022-10-11 f41df6b57d2c4b090a84427e02950df84fb385ad

View File

@@ -39,5 +39,4 @@ content:
for spec in x86_32 x86_64 arm arm_64; do \ for spec in x86_32 x86_64 arm arm_64; do \
mv lib/mk/spec/$$spec/ld-foc.mk lib/mk/spec/$$spec/ld.mk; \ mv lib/mk/spec/$$spec/ld-foc.mk lib/mk/spec/$$spec/ld.mk; \
done; done;
sed -i "s/ld-foc/ld/" src/lib/ld/foc/target.mk
sed -i "s/foc_timer_drv/timer/" src/timer/foc/target.mk sed -i "s/foc_timer_drv/timer/" src/timer/foc/target.mk

View File

@@ -1,2 +0,0 @@
TARGET = ld-foc
LIBS = ld-foc

View File

@@ -0,0 +1 @@
arm_v8a

View File

@@ -0,0 +1 @@
0x40000000

View File

@@ -1,6 +1,6 @@
REP_INC_DIR += src/bootstrap/board/virt_qemu REP_INC_DIR += src/bootstrap/board/virt_qemu_arm_v7a
SRC_CC += bootstrap/board/virt_qemu/platform.cc SRC_CC += bootstrap/board/virt_qemu_arm_v7a/platform.cc
SRC_CC += bootstrap/spec/arm/arm_v7_cpu.cc SRC_CC += bootstrap/spec/arm/arm_v7_cpu.cc
SRC_CC += bootstrap/spec/arm/cortex_a15_cpu.cc SRC_CC += bootstrap/spec/arm/cortex_a15_cpu.cc
SRC_CC += bootstrap/spec/arm/gicv2.cc SRC_CC += bootstrap/spec/arm/gicv2.cc

View File

@@ -1,4 +1,4 @@
REP_INC_DIR += src/core/board/virt_qemu REP_INC_DIR += src/core/board/virt_qemu_arm_v7a
REP_INC_DIR += src/core/spec/arm/virtualization REP_INC_DIR += src/core/spec/arm/virtualization
# add C++ sources # add C++ sources

View File

@@ -1,8 +1,8 @@
REP_INC_DIR += src/bootstrap/board/virt_qemu_64 REP_INC_DIR += src/bootstrap/board/virt_qemu_arm_v8a
SRC_CC += bootstrap/spec/arm/gicv3.cc SRC_CC += bootstrap/spec/arm/gicv3.cc
SRC_CC += bootstrap/spec/arm_64/cortex_a53_mmu.cc SRC_CC += bootstrap/spec/arm_64/cortex_a53_mmu.cc
SRC_CC += bootstrap/board/virt_qemu_64/platform.cc SRC_CC += bootstrap/board/virt_qemu_arm_v8a/platform.cc
SRC_CC += lib/base/arm_64/kernel/interface.cc SRC_CC += lib/base/arm_64/kernel/interface.cc
SRC_CC += spec/64bit/memory_map.cc SRC_CC += spec/64bit/memory_map.cc
SRC_S += bootstrap/spec/arm_64/crt0.s SRC_S += bootstrap/spec/arm_64/crt0.s

View File

@@ -1,4 +1,4 @@
REP_INC_DIR += src/core/board/virt_qemu_64 REP_INC_DIR += src/core/board/virt_qemu_arm_v8a
REP_INC_DIR += src/core/spec/arm/virtualization REP_INC_DIR += src/core/spec/arm/virtualization
# add C++ sources # add C++ sources

View File

@@ -4,6 +4,6 @@ SRC_CC += timer_connection_time.cc
SRC_CC += hw/timer_connection_timestamp.cc SRC_CC += hw/timer_connection_timestamp.cc
SRC_CC += duration.cc SRC_CC += duration.cc
INC_DIR += $(BASE_DIR)/src/include REP_INC_DIR += src/include
vpath % $(BASE_DIR)/src/lib/timeout vpath % $(call select_from_repositories,src/lib/timeout)

View File

@@ -1 +1 @@
2022-05-24 ab1cb582165e76bda4abf27870f44ad7d1ae5b6d 2022-10-11 50db06fe21eca6c46c9b4bf7fcbc81538ac74f32

View File

@@ -1,4 +1,6 @@
CONTENT += src/core/board/imx53_qsb \ CONTENT += src/core/board/imx53_qsb \
src/bootstrap/board/imx53_qsb src/bootstrap/board/imx53_qsb \
lib/mk/spec/arm_v7/core-hw-imx53_qsb.inc \
lib/mk/spec/arm_v7/bootstrap-hw-imx53_qsb.inc
include $(GENODE_DIR)/repos/base-hw/recipes/src/base-hw_content.inc include $(GENODE_DIR)/repos/base-hw/recipes/src/base-hw_content.inc

View File

@@ -1 +1 @@
2022-05-24 0fff6ce83b962b3fd54cf6eda0a157cb0cb0c9d5 2022-10-11 1377d3a2b7afaa265cc5ae6bbd515679be527c40

View File

@@ -1 +1 @@
2022-05-24 8c17512664a648eaed876c815ea678770eda3280 2022-10-11 c32cf899ce00bd69aff5bbd4f7b6b611d2bfa47d

View File

@@ -1 +1 @@
2022-05-24 edc396d9bc9a2ebf73590e70c1363020226909be 2022-10-11 39ff297bc573b8e8bf4f2e6e233bf0b1b21f13af

View File

@@ -1 +1 @@
2022-05-24 da90478c4c0b8993041bc59488eedb124e680e78 2022-10-11 f5456c3ed55b53ccaefee603fdb8d9b1e3ca84ab

View File

@@ -1 +1 @@
2022-05-24 1b34e317209c48bfc88af6118db32be261ce3e0c 2022-10-11 de2f50d9164952dbbf6ce76d29abad5d96da8512

View File

@@ -1 +1 @@
2022-05-24 46e9f88209bbc95228d3882cc0831770315402e4 2022-10-11 5d72eb4e34f582c06c086345b225cee91ce539cc

View File

@@ -1,4 +0,0 @@
CONTENT += src/core/board/virt_qemu_64 \
src/bootstrap/board/virt_qemu_64
include $(GENODE_DIR)/repos/base-hw/recipes/src/base-hw_content.inc

View File

@@ -1 +0,0 @@
2022-05-24 bb6c39c093a24d2ec4ff1d00e397529c51e95fa7

View File

@@ -0,0 +1,4 @@
CONTENT += src/core/board/virt_qemu_arm_v7a \
src/bootstrap/board/virt_qemu_arm_v7a
include $(GENODE_DIR)/repos/base-hw/recipes/src/base-hw_content.inc

View File

@@ -0,0 +1 @@
2022-10-11 70e53c98ef4b3215440efb2ea09e07ff7cd97c4f

View File

@@ -0,0 +1,4 @@
CONTENT += src/core/board/virt_qemu_arm_v8a \
src/bootstrap/board/virt_qemu_arm_v8a
include $(GENODE_DIR)/repos/base-hw/recipes/src/base-hw_content.inc

View File

@@ -0,0 +1 @@
2022-10-11 c146d70c9bde3f928110c868a54b8c800beffd79

View File

@@ -0,0 +1,2 @@
base-hw
base

View File

@@ -115,6 +115,12 @@ SRC_LIB_BASE += $(notdir $(wildcard $(BASE_HW_DIR)/src/lib/base/*.cc)) \
$(notdir $(wildcard $(BASE_DIR)/src/lib/base/*.cc)) \ $(notdir $(wildcard $(BASE_DIR)/src/lib/base/*.cc)) \
${call selected_content,SRC_LIB_BASE_SPECS} ${call selected_content,SRC_LIB_BASE_SPECS}
SRC_LIB_TIMEOUT += duration.cc \
hw/timer_connection_timestamp.cc \
timeout.cc \
timer_connection.cc \
timer_connection_time.cc
SRC_LIB_STARTUP += init_main_thread.cc _main.cc \ SRC_LIB_STARTUP += init_main_thread.cc _main.cc \
$(addprefix spec/,${call selected_content,SRC_LIB_STARTUP_SPECS}) $(addprefix spec/,${call selected_content,SRC_LIB_STARTUP_SPECS})
@@ -125,15 +131,24 @@ SRC_CORE += $(notdir $(wildcard $(BASE_HW_DIR)/src/core/*.cc)) \
$(addprefix board/,$(BOARD)) \ $(addprefix board/,$(BOARD)) \
version.inc target.inc include hw kernel version.inc target.inc include hw kernel
LIB_MK := base-hw-common.mk base-hw.mk bootstrap-hw.inc core-hw.inc \ # names of the lib/mk/ files to consider for inclusion in the src archive
timeout-hw.mk cxx.mk base.inc base-common.inc startup.inc \ LIB_MK_FILES := base-common.inc base-hw-common.mk \
$(addprefix spec/,${call selected_content,LIB_MK_SPECS}) base.inc base-hw.mk \
bootstrap-hw.inc bootstrap-hw-$(BOARD).inc bootstrap-hw-$(BOARD).mk \
core-hw.inc core-hw-$(BOARD).inc core-hw-$(BOARD).mk \
startup.inc startup-hw.mk \
timeout-hw.mk cxx.mk ld-hw.mk syscall-hw.mk
LIB_MK_DIRS := lib/mk $(addprefix lib/mk/spec/,${call selected_content,LIB_MK_SPECS})
CONTENT += $(foreach D,$(LIB_MK_DIRS),$(addprefix $D/,$(LIB_MK_FILES)))
CONTENT += $(addprefix src/timer/,$(SRC_TIMER)) \ CONTENT += $(addprefix src/timer/,$(SRC_TIMER)) \
$(addprefix src/include/hw/,$(SRC_INCLUDE_HW)) \ $(addprefix src/include/hw/,$(SRC_INCLUDE_HW)) \
$(addprefix src/bootstrap/,$(SRC_BOOTSTRAP)) \ $(addprefix src/bootstrap/,$(SRC_BOOTSTRAP)) \
$(addprefix lib/mk/,$(LIB_MK)) \ $(addprefix lib/mk/,$(LIB_MK)) \
$(addprefix src/lib/base/,$(SRC_LIB_BASE)) \ $(addprefix src/lib/base/,$(SRC_LIB_BASE)) \
$(addprefix src/lib/timeout/,$(SRC_LIB_TIMEOUT)) \
$(addprefix src/lib/startup/,$(SRC_LIB_STARTUP)) \ $(addprefix src/lib/startup/,$(SRC_LIB_STARTUP)) \
$(addprefix src/core/,$(SRC_CORE)) \ $(addprefix src/core/,$(SRC_CORE)) \
src/lib/hw src/lib/ld src/lib/cxx \ src/lib/hw src/lib/ld src/lib/cxx \
@@ -180,7 +195,6 @@ generalize_target_names: $(CONTENT)
# apply kernel-agnostic convention of naming the timer and ld.lib.so # apply kernel-agnostic convention of naming the timer and ld.lib.so
for subdir in ${call selected_content,LD_MK_DIRS}; do \ for subdir in ${call selected_content,LD_MK_DIRS}; do \
mv $$subdir/ld-hw.mk $$subdir/ld.mk; done mv $$subdir/ld-hw.mk $$subdir/ld.mk; done
sed -i "s/ld-hw/ld/" src/lib/ld/hw/target.mk
sed -i "s/hw_timer_drv/timer/" src/timer/hw/target.mk sed -i "s/hw_timer_drv/timer/" src/timer/hw/target.mk
# supplement BOARD definition that normally comes form the build dir # supplement BOARD definition that normally comes form the build dir
sed -i "s/\?= unknown/:= $(BOARD)/" src/core/hw/target.mk sed -i "s/\?= unknown/:= $(BOARD)/" src/core/hw/target.mk
@@ -189,5 +203,4 @@ generalize_target_names: $(CONTENT)
sed -i "1aREQUIRES := $(ARCH)" src/core/hw/target.mk sed -i "1aREQUIRES := $(ARCH)" src/core/hw/target.mk
sed -i "1aREQUIRES := $(ARCH)" src/bootstrap/hw/target.mk sed -i "1aREQUIRES := $(ARCH)" src/bootstrap/hw/target.mk
sed -i "/REQUIRES/s/hw/hw $(ARCH)/" src/timer/hw/target.mk sed -i "/REQUIRES/s/hw/hw $(ARCH)/" src/timer/hw/target.mk
sed -i "1aREQUIRES := $(ARCH)" src/lib/ld/hw/target.mk

View File

@@ -109,7 +109,8 @@ Cpu::Idle_thread::Idle_thread(Board::Address_space_id_allocator &addr_space_id_a
Cpu &cpu, Cpu &cpu,
Pd &core_pd) Pd &core_pd)
: :
Thread { addr_space_id_alloc, user_irq_pool, cpu_pool, core_pd, "idle" } Thread { addr_space_id_alloc, user_irq_pool, cpu_pool, core_pd,
Cpu_priority::min(), 0, "idle", Thread::IDLE }
{ {
regs->ip = (addr_t)&idle_thread_main; regs->ip = (addr_t)&idle_thread_main;
@@ -120,14 +121,9 @@ Cpu::Idle_thread::Idle_thread(Board::Address_space_id_allocator &addr_space_id_a
void Cpu::schedule(Job * const job) void Cpu::schedule(Job * const job)
{ {
if (_id == executing_id()) _scheduler.ready(job->share());
_scheduler.ready(job->share()); if (_id != executing_id() && _scheduler.need_to_schedule())
else { trigger_ip_interrupt();
_scheduler.ready_check(job->share());
if (_scheduler.need_to_schedule())
trigger_ip_interrupt();
}
} }

View File

@@ -191,45 +191,17 @@ void Cpu_scheduler::update(time_t time)
} }
void Cpu_scheduler::ready_check(Share &s1)
{
assert(_head);
ready(s1);
if (_need_to_schedule)
return;
Share * s2 = _head;
if (!s1._claim) {
_need_to_schedule = s2 == &_idle;
} else if (!_head_claims) {
_need_to_schedule = true;
} else if (s1._prio != s2->_prio) {
_need_to_schedule = s1._prio > s2->_prio;
} else {
for (
; s2 && s2 != &s1;
s2 =
Double_list<Cpu_share>::next(&s2->_claim_item) != nullptr ?
&Double_list<Cpu_share>::next(&s2->_claim_item)->payload() :
nullptr) ;
_need_to_schedule = !s2;
}
}
void Cpu_scheduler::ready(Share &s) void Cpu_scheduler::ready(Share &s)
{ {
assert(!s._ready && &s != &_idle); assert(!s._ready && &s != &_idle);
_need_to_schedule = true;
s._ready = 1; s._ready = 1;
s._fill = _fill; s._fill = _fill;
_fills.insert_tail(&s._fill_item); _fills.insert_tail(&s._fill_item);
if (_head == &_idle)
_need_to_schedule = true;
if (!s._quota) if (!s._quota)
return; return;
@@ -239,6 +211,28 @@ void Cpu_scheduler::ready(Share &s)
_rcl[s._prio].insert_head(&s._claim_item); _rcl[s._prio].insert_head(&s._claim_item);
else else
_rcl[s._prio].insert_tail(&s._claim_item); _rcl[s._prio].insert_tail(&s._claim_item);
/*
* Check whether we need to re-schedule
*/
if (_need_to_schedule)
return;
/* current head has no quota left */
if (!_head_claims) {
_need_to_schedule = true;
return;
}
/* if current head has different priority */
if (s._prio != _head->_prio) {
_need_to_schedule = s._prio > _head->_prio;
return;
}
/* if current head has same priority, the ready share gets active */
if (s._claim)
_need_to_schedule = true;
} }
@@ -246,7 +240,8 @@ void Cpu_scheduler::unready(Share &s)
{ {
assert(s._ready && &s != &_idle); assert(s._ready && &s != &_idle);
_need_to_schedule = true; if (&s == _head)
_need_to_schedule = true;
s._ready = 0; s._ready = 0;
_fills.remove(&s._fill_item); _fills.remove(&s._fill_item);
@@ -270,21 +265,15 @@ void Cpu_scheduler::remove(Share &s)
{ {
assert(&s != &_idle); assert(&s != &_idle);
_need_to_schedule = true; if (s._ready) unready(s);
if (&s == _head) if (&s == _head)
_head = nullptr; _head = nullptr;
if (s._ready)
_fills.remove(&s._fill_item);
if (!s._quota) if (!s._quota)
return; return;
if (s._ready) _ucl[s._prio].remove(&s._claim_item);
_rcl[s._prio].remove(&s._claim_item);
else
_ucl[s._prio].remove(&s._claim_item);
} }
@@ -292,8 +281,6 @@ void Cpu_scheduler::insert(Share &s)
{ {
assert(!s._ready); assert(!s._ready);
_need_to_schedule = true;
if (!s._quota) if (!s._quota)
return; return;

View File

@@ -180,15 +180,10 @@ class Kernel::Cpu_scheduler
void timeout() { _need_to_schedule = true; } void timeout() { _need_to_schedule = true; }
/** /**
* Update head according to the consumed time * Update head according to the current (absolute) time
*/ */
void update(time_t time); void update(time_t time);
/**
* Set 's1' ready and return wether this outdates current head
*/
void ready_check(Share &s1);
/** /**
* Set share 's' ready * Set share 's' ready
*/ */

View File

@@ -329,6 +329,23 @@ void Thread::_call_start_thread()
/* join protection domain */ /* join protection domain */
thread._pd = (Pd *) user_arg_3(); thread._pd = (Pd *) user_arg_3();
thread._ipc_init(*(Native_utcb *)user_arg_4(), *this); thread._ipc_init(*(Native_utcb *)user_arg_4(), *this);
/*
* Sanity check core threads!
*
* Currently, the model assumes that there is only one core
* entrypoint, which serves requests, and which can destroy
* threads and pds. If this changes, we have to inform all
* cpus about pd destructions to remove their page-tables
* from the hardware in case that a core-thread running with
* that same pd is currently active. Therefore, warn if the
* semantic changes, and additional core threads are started
* across cpu cores.
*/
if (thread._pd == &_core_pd && cpu.id() != _cpu_pool.primary_cpu().id())
Genode::raw("Error: do not start core threads"
" on CPU cores different than boot cpu");
thread._become_active(); thread._become_active();
} }
@@ -369,7 +386,7 @@ void Thread::_call_restart_thread()
Thread &thread = *thread_ptr; Thread &thread = *thread_ptr;
if (!_core && (&pd() != &thread.pd())) { if (_type == USER && (&pd() != &thread.pd())) {
raw(*this, ": failed to lookup thread ", (unsigned)user_arg_1(), raw(*this, ": failed to lookup thread ", (unsigned)user_arg_1(),
" to restart it"); " to restart it");
_die(); _die();
@@ -447,6 +464,18 @@ void Thread::_call_delete_thread()
} }
void Thread::_call_delete_pd()
{
Genode::Kernel_object<Pd> & pd =
*(Genode::Kernel_object<Pd>*)user_arg_1();
if (_cpu->active(pd->mmu_regs))
_cpu->switch_to(_core_pd.mmu_regs);
_call_delete<Pd>();
}
void Thread::_call_await_request_msg() void Thread::_call_await_request_msg()
{ {
if (_ipc_node.can_await_request()) { if (_ipc_node.can_await_request()) {
@@ -793,7 +822,7 @@ void Thread::_call()
case call_id_pause_vm(): _call_pause_vm(); return; case call_id_pause_vm(): _call_pause_vm(); return;
default: default:
/* check wether this is a core thread */ /* check wether this is a core thread */
if (!_core) { if (_type != CORE) {
Genode::raw(*this, ": not entitled to do kernel call"); Genode::raw(*this, ": not entitled to do kernel call");
_die(); _die();
return; return;
@@ -805,7 +834,7 @@ void Thread::_call()
_call_new<Thread>(_addr_space_id_alloc, _user_irq_pool, _cpu_pool, _call_new<Thread>(_addr_space_id_alloc, _user_irq_pool, _cpu_pool,
_core_pd, (unsigned) user_arg_2(), _core_pd, (unsigned) user_arg_2(),
(unsigned) _core_to_kernel_quota(user_arg_3()), (unsigned) _core_to_kernel_quota(user_arg_3()),
(char const *) user_arg_4()); (char const *) user_arg_4(), USER);
return; return;
case call_id_new_core_thread(): case call_id_new_core_thread():
_call_new<Thread>(_addr_space_id_alloc, _user_irq_pool, _cpu_pool, _call_new<Thread>(_addr_space_id_alloc, _user_irq_pool, _cpu_pool,
@@ -822,7 +851,7 @@ void Thread::_call()
*(Genode::Platform_pd *) user_arg_3(), *(Genode::Platform_pd *) user_arg_3(),
_addr_space_id_alloc); _addr_space_id_alloc);
return; return;
case call_id_delete_pd(): _call_delete<Pd>(); return; case call_id_delete_pd(): _call_delete_pd(); return;
case call_id_new_signal_receiver(): _call_new<Signal_receiver>(); return; case call_id_new_signal_receiver(): _call_new<Signal_receiver>(); return;
case call_id_new_signal_context(): case call_id_new_signal_context():
_call_new<Signal_context>(*(Signal_receiver*) user_arg_2(), user_arg_3()); _call_new<Signal_context>(*(Signal_receiver*) user_arg_2(), user_arg_3());
@@ -857,7 +886,7 @@ void Thread::_mmu_exception()
return; return;
} }
if (_core) if (_type != USER)
Genode::raw(*this, " raised a fault, which should never happen ", Genode::raw(*this, " raised a fault, which should never happen ",
_fault); _fault);
@@ -874,7 +903,7 @@ Thread::Thread(Board::Address_space_id_allocator &addr_space_id_alloc,
unsigned const priority, unsigned const priority,
unsigned const quota, unsigned const quota,
char const *const label, char const *const label,
bool core) Type type)
: :
Kernel::Object { *this }, Kernel::Object { *this },
Cpu_job { priority, quota }, Cpu_job { priority, quota },
@@ -885,8 +914,8 @@ Thread::Thread(Board::Address_space_id_allocator &addr_space_id_alloc,
_ipc_node { *this }, _ipc_node { *this },
_state { AWAITS_START }, _state { AWAITS_START },
_label { label }, _label { label },
_core { core }, _type { type },
regs { core } regs { type != USER }
{ } { }

View File

@@ -57,6 +57,10 @@ struct Kernel::Thread_fault
*/ */
class Kernel::Thread : private Kernel::Object, public Cpu_job, private Timeout class Kernel::Thread : private Kernel::Object, public Cpu_job, private Timeout
{ {
public:
enum Type { USER, CORE, IDLE };
private: private:
/* /*
@@ -149,7 +153,7 @@ class Kernel::Thread : private Kernel::Object, public Cpu_job, private Timeout
capid_t _timeout_sigid { 0 }; capid_t _timeout_sigid { 0 };
bool _paused { false }; bool _paused { false };
bool _cancel_next_await_signal { false }; bool _cancel_next_await_signal { false };
bool const _core { false }; Type const _type;
Genode::Constructible<Tlb_invalidation> _tlb_invalidation {}; Genode::Constructible<Tlb_invalidation> _tlb_invalidation {};
Genode::Constructible<Destroy> _destroy {}; Genode::Constructible<Destroy> _destroy {};
@@ -230,6 +234,7 @@ class Kernel::Thread : private Kernel::Object, public Cpu_job, private Timeout
void _call_restart_thread(); void _call_restart_thread();
void _call_yield_thread(); void _call_yield_thread();
void _call_delete_thread(); void _call_delete_thread();
void _call_delete_pd();
void _call_await_request_msg(); void _call_await_request_msg();
void _call_send_request_msg(); void _call_send_request_msg();
void _call_send_reply_msg(); void _call_send_reply_msg();
@@ -301,7 +306,7 @@ class Kernel::Thread : private Kernel::Object, public Cpu_job, private Timeout
unsigned const priority, unsigned const priority,
unsigned const quota, unsigned const quota,
char const *const label, char const *const label,
bool core = false); Type const type);
/** /**
* Constructor for core/kernel thread * Constructor for core/kernel thread
@@ -315,7 +320,7 @@ class Kernel::Thread : private Kernel::Object, public Cpu_job, private Timeout
char const *const label) char const *const label)
: :
Thread(addr_space_id_alloc, user_irq_pool, cpu_pool, core_pd, Thread(addr_space_id_alloc, user_irq_pool, cpu_pool, core_pd,
Cpu_priority::min(), 0, label, true) Cpu_priority::min(), 0, label, CORE)
{ } { }
~Thread(); ~Thread();
@@ -432,6 +437,7 @@ class Kernel::Thread : private Kernel::Object, public Cpu_job, private Timeout
char const * label() const { return _label; } char const * label() const { return _label; }
Thread_fault fault() const { return _fault; } Thread_fault fault() const { return _fault; }
Genode::Native_utcb *utcb() { return _utcb; } Genode::Native_utcb *utcb() { return _utcb; }
Type type() const { return _type; }
Pd &pd() const Pd &pd() const
{ {

View File

@@ -77,6 +77,8 @@ class Kernel::Vm : private Kernel::Object, public Cpu_job
Kernel::Signal_context & context, Kernel::Signal_context & context,
Identity & id); Identity & id);
~Vm();
/** /**
* Inject an interrupt to this VM * Inject an interrupt to this VM
* *

View File

@@ -22,13 +22,17 @@
using namespace Genode; using namespace Genode;
using namespace Kernel; using namespace Kernel;
using Device = Board::Timer;
using Device = Board::Timer;
using counter_t = Board::Timer::Counter::access_t;
enum { enum {
TICS_PER_MS = TICS_PER_MS =
Board::CORTEX_A9_PRIVATE_TIMER_CLK / Board::CORTEX_A9_PRIVATE_TIMER_CLK /
Board::CORTEX_A9_PRIVATE_TIMER_DIV / 1000 Board::CORTEX_A9_PRIVATE_TIMER_DIV / 1000,
MAX_COUNTER_VAL = ~(counter_t)0
}; };
@@ -79,12 +83,33 @@ time_t Timer::us_to_ticks(time_t const us) const {
time_t Timer::_duration() const time_t Timer::_duration() const
{ {
Device::Counter::access_t last = _last_timeout_duration; counter_t const start_counter_val { (counter_t)_last_timeout_duration };
Device::Counter::access_t cnt = _device.read<Device::Counter>(); counter_t const curr_counter_val { _device.read<Device::Counter>() };
Device::Counter::access_t ret = (_device.read<Device::Interrupt_status::Event>())
? _max_value() - cnt + last : last - cnt; /*
return ret; * Calculate result depending on whether the counter already wrapped or
* not. See the comment in the implementation of '_max_value' for an
* explanation why this comparison is done instead of checking the IRQ
* status and why it is sufficient.
*/
if (curr_counter_val > start_counter_val)
return start_counter_val + (MAX_COUNTER_VAL - curr_counter_val);
return start_counter_val - curr_counter_val;
} }
time_t Timer::_max_value() const { return 0xfffffffe; } time_t Timer::_max_value() const
{
/*
* We propagate a max timeout value far lower than the one required
* by the hardware. This is because on some platforms (Qemu 4.2.1 PBXA9),
* the IRQ status register is not reliable. Sometimes, it indicates an IRQ
* too early, i.e., shortly before the counter wraps. Therefore we have to
* accomplish wrap detection via counter comparison only. Therefore, we
* have to make sure that we always read out the counter before it hits
* the max timout value again. And, therefore, the max timeout value has
* to be far away from the first value the counter has after wrapping.
*/
return MAX_COUNTER_VAL >> 1;
}

View File

@@ -87,24 +87,25 @@ void Arm_cpu::mmu_fault_status(Fsr::access_t fsr, Thread_fault & fault)
} }
void Arm_cpu::switch_to(Arm_cpu::Context&, Arm_cpu::Mmu_context & o) bool Arm_cpu::active(Arm_cpu::Mmu_context & ctx)
{ {
if (o.cidr == 0) return; return (Cidr::read() == ctx.cidr);
}
Cidr::access_t cidr = Cidr::read();
if (cidr != o.cidr) { void Arm_cpu::switch_to(Arm_cpu::Mmu_context & ctx)
/** {
* First switch to global mappings only to prevent /**
* that wrong branch predicts result due to ASID * First switch to global mappings only to prevent
* and Page-Table not being in sync (see ARM RM B 3.10.4) * that wrong branch predicts result due to ASID
*/ * and Page-Table not being in sync (see ARM RM B 3.10.4)
Cidr::write(0); */
Cpu::synchronization_barrier(); Cidr::write(0);
Ttbr0::write(o.ttbr0); Cpu::synchronization_barrier();
Cpu::synchronization_barrier(); Ttbr0::write(ctx.ttbr0);
Cidr::write(o.cidr); Cpu::synchronization_barrier();
Cpu::synchronization_barrier(); Cidr::write(ctx.cidr);
} Cpu::synchronization_barrier();
} }

View File

@@ -104,7 +104,8 @@ struct Genode::Arm_cpu : public Hw::Arm_cpu
else Tlbiall::write(0); else Tlbiall::write(0);
} }
void switch_to(Context&, Mmu_context & o); bool active(Mmu_context &);
void switch_to(Mmu_context &);
static void mmu_fault(Context & c, Kernel::Thread_fault & fault); static void mmu_fault(Context & c, Kernel::Thread_fault & fault);
static void mmu_fault_status(Fsr::access_t fsr, static void mmu_fault_status(Fsr::access_t fsr,

View File

@@ -67,7 +67,8 @@ void Kernel::Thread::Tlb_invalidation::execute() { };
void Thread::proceed(Cpu & cpu) void Thread::proceed(Cpu & cpu)
{ {
cpu.switch_to(*regs, pd().mmu_regs); if (!cpu.active(pd().mmu_regs) && type() != CORE)
cpu.switch_to(pd().mmu_regs);
regs->cpu_exception = cpu.stack_start(); regs->cpu_exception = cpu.stack_start();
kernel_to_user_context_switch((static_cast<Cpu::Context*>(&*regs)), kernel_to_user_context_switch((static_cast<Cpu::Context*>(&*regs)),

View File

@@ -38,6 +38,9 @@ Vm::Vm(Irq::Pool & user_irq_pool,
} }
Vm::~Vm() {}
void Vm::exception(Cpu & cpu) void Vm::exception(Cpu & cpu)
{ {
switch(_state.cpu_exception) { switch(_state.cpu_exception) {

View File

@@ -15,7 +15,7 @@
push { r0 } push { r0 }
mrc p15, 4, r0, c1, c1, 0 /* read HCR register */ mrc p15, 4, r0, c1, c1, 0 /* read HCR register */
tst r0, #1 /* check VM bit */ tst r0, #1 /* check VM bit */
beq _host_to_vm beq _from_host
mov r0, #\exception_type mov r0, #\exception_type
b _vm_to_host b _vm_to_host
.endm /* _vm_exit */ .endm /* _vm_exit */
@@ -47,10 +47,27 @@ _vt_dab_entry: _vm_exit 5
_vt_irq_entry: _vm_exit 6 _vt_irq_entry: _vm_exit 6
_vt_trp_entry: _vm_exit 8 _vt_trp_entry: _vm_exit 8
_host_to_vm: _from_host:
pop { r0 }
cmp r0, #0
beq _to_vm
cmp r0, #1
beq _invalidate_tlb
eret
_invalidate_tlb:
push { r3, r4 }
mrrc p15, 6, r3, r4, c2 /* save VTTBR */
mcrr p15, 6, r1, r2, c2 /* write VTTBR */
mcr p15, 0, r0, c8, c3, 0 /* TLBIALLIS */
mcrr p15, 6, r3, r4, c2 /* restore VTTBR */
eret
_to_vm:
push { r1 } push { r1 }
ldr r0, [sp, #1*4] push { r2 }
add r0, r0, #13*4 add r0, r1, #13*4
ldmia r0!, { r1-r5 } ldmia r0!, { r1-r5 }
msr sp_usr, r1 msr sp_usr, r1
mov lr, r2 mov lr, r2
@@ -115,6 +132,7 @@ _host_to_vm:
ldmia r0, {r0-r12} /* load vm's r0-r12 */ ldmia r0, {r0-r12} /* load vm's r0-r12 */
eret eret
_vm_to_host: _vm_to_host:
push { r0 } /* push cpu excep. */ push { r0 } /* push cpu excep. */
ldr r0, [sp, #3*4] /* load vm state ptr */ ldr r0, [sp, #3*4] /* load vm state ptr */
@@ -218,6 +236,7 @@ _vm_to_host:
/* host kernel must jump to this point to switch to a vm */ /* host kernel must jump to this point to switch to a vm */
.global hypervisor_enter_vm .global hypervisor_call
hypervisor_enter_vm: hypervisor_call:
hvc #0 hvc #0
bx lr

View File

@@ -0,0 +1,55 @@
/*
* \brief Interface between kernel and hypervisor
* \author Stefan Kalkowski
* \date 2022-06-13
*/
/*
* Copyright (C) 2022 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _SPEC__ARM_V7__VIRTUALIZATION_HYPERVISOR_H_
#define _SPEC__ARM_V7__VIRTUALIZATION_HYPERVISOR_H_
#include <base/stdint.h>
#include <cpu/vm_state_virtualization.h>
namespace Hypervisor {
struct Host_context;
enum Call_number {
WORLD_SWITCH = 0,
TLB_INVALIDATE = 1,
};
using Call_arg = Genode::umword_t;
using Call_ret = Genode::umword_t;
extern "C"
Call_ret hypervisor_call(Call_arg call_id,
Call_arg arg0,
Call_arg arg1);
inline void invalidate_tlb(Genode::uint64_t vttbr)
{
hypervisor_call(TLB_INVALIDATE,
(vttbr & 0xffffffff),
((vttbr >> 32U) & 0xffffffff));
}
inline void switch_world(Genode::Vm_state & vm_state,
Host_context & host_state)
{
hypervisor_call(WORLD_SWITCH,
(Call_arg)&vm_state,
(Call_arg)&host_state);
}
}
#endif /* _SPEC__ARM_V7__VIRTUALIZATION_HYPERVISOR_H_ */

View File

@@ -19,6 +19,7 @@
#include <kernel/cpu.h> #include <kernel/cpu.h>
#include <kernel/vm.h> #include <kernel/vm.h>
#include <kernel/main.h> #include <kernel/main.h>
#include <spec/arm_v7/virtualization/hypervisor.h>
namespace Kernel { namespace Kernel {
@@ -41,7 +42,7 @@ namespace Kernel {
using namespace Kernel; using namespace Kernel;
struct Host_context struct Hypervisor::Host_context
{ {
Cpu::Ttbr_64bit::access_t vttbr; Cpu::Ttbr_64bit::access_t vttbr;
Cpu::Hcr::access_t hcr; Cpu::Hcr::access_t hcr;
@@ -61,15 +62,13 @@ struct Host_context
} vt_host_context; } vt_host_context;
extern "C" void hypervisor_enter_vm(Genode::Vm_state&, Host_context&); static Hypervisor::Host_context & host_context(Cpu & cpu)
static Host_context & host_context(Cpu & cpu)
{ {
static Genode::Constructible<Host_context> host_context[NR_OF_CPUS]; static Genode::Constructible<Hypervisor::Host_context>
host_context[NR_OF_CPUS];
if (!host_context[cpu.id()].constructed()) { if (!host_context[cpu.id()].constructed()) {
host_context[cpu.id()].construct(); host_context[cpu.id()].construct();
Host_context & c = *host_context[cpu.id()]; Hypervisor::Host_context & c = *host_context[cpu.id()];
c.sp = cpu.stack_start(); c.sp = cpu.stack_start();
c.ttbr0 = Cpu::Ttbr0_64bit::read(); c.ttbr0 = Cpu::Ttbr0_64bit::read();
c.ttbr1 = Cpu::Ttbr1_64bit::read(); c.ttbr1 = Cpu::Ttbr1_64bit::read();
@@ -152,6 +151,15 @@ Kernel::Vm::Vm(Irq::Pool & user_irq_pool,
} }
Kernel::Vm::~Vm()
{
Cpu::Ttbr_64bit::access_t vttbr =
Cpu::Ttbr_64bit::Ba::masked((Cpu::Ttbr_64bit::access_t)_id.table);
Cpu::Ttbr_64bit::Asid::set(vttbr, _id.id);
Hypervisor::invalidate_tlb(vttbr);
}
void Kernel::Vm::exception(Cpu & cpu) void Kernel::Vm::exception(Cpu & cpu)
{ {
switch(_state.cpu_exception) { switch(_state.cpu_exception) {
@@ -190,7 +198,7 @@ void Kernel::Vm::proceed(Cpu & cpu)
_state.esr_el2 = Cpu::Hstr::init(); _state.esr_el2 = Cpu::Hstr::init();
_state.hpfar_el2 = Cpu::Hcr::init(); _state.hpfar_el2 = Cpu::Hcr::init();
hypervisor_enter_vm(_state, host_context(cpu)); Hypervisor::switch_world(_state, host_context(cpu));
} }

View File

@@ -26,12 +26,15 @@ Genode::Cpu::Context::Context(bool privileged)
} }
void Genode::Cpu::switch_to(Context&, Mmu_context & mmu_context) bool Genode::Cpu::active(Mmu_context & mmu_context)
{ {
if (mmu_context.id() == 0) return; return (mmu_context.id() == Ttbr::Asid::get(Ttbr0_el1::read()));
}
if (mmu_context.id() != Ttbr::Asid::get(Ttbr0_el1::read()))
Ttbr0_el1::write(mmu_context.ttbr); void Genode::Cpu::switch_to(Mmu_context & mmu_context)
{
Ttbr0_el1::write(mmu_context.ttbr);
} }

View File

@@ -99,7 +99,8 @@ struct Genode::Cpu : Hw::Arm_64_cpu
return Ttbr::Asid::get(ttbr) & 0xffff; } return Ttbr::Asid::get(ttbr) & 0xffff; }
}; };
void switch_to(Context&, Mmu_context &); bool active(Mmu_context &);
void switch_to(Mmu_context &);
static void mmu_fault(Context &, Kernel::Thread_fault &); static void mmu_fault(Context &, Kernel::Thread_fault &);

View File

@@ -55,6 +55,16 @@ void Thread::exception(Cpu & cpu)
" ISS=", Cpu::Esr::Iss::get(esr), " ISS=", Cpu::Esr::Iss::get(esr),
" ip=", (void*)regs->ip); " ip=", (void*)regs->ip);
}; };
/*
* If the machine exception is caused by a non-privileged
* component, mark it dead, and continue execution.
*/
if (regs->exception_type == Cpu::SYNC_LEVEL_EL0) {
Genode::raw("Will freeze thread ", *this);
_become_inactive(DEAD);
return;
}
break; break;
} }
default: default:
@@ -75,10 +85,14 @@ void Thread::exception(Cpu & cpu)
void Kernel::Thread::Tlb_invalidation::execute() { }; void Kernel::Thread::Tlb_invalidation::execute() { };
bool Kernel::Pd::invalidate_tlb(Cpu &, addr_t addr, size_t size) bool Kernel::Pd::invalidate_tlb(Cpu & cpu, addr_t addr, size_t size)
{ {
using namespace Genode; using namespace Genode;
/* only apply to the active cpu */
if (cpu.id() != Cpu::executing_id())
return false;
/** /**
* The kernel part of the address space is mapped as global * The kernel part of the address space is mapped as global
* therefore we have to invalidate it differently * therefore we have to invalidate it differently
@@ -108,7 +122,9 @@ bool Kernel::Pd::invalidate_tlb(Cpu &, addr_t addr, size_t size)
void Thread::proceed(Cpu & cpu) void Thread::proceed(Cpu & cpu)
{ {
cpu.switch_to(*regs, pd().mmu_regs); if (!cpu.active(pd().mmu_regs) && type() != CORE)
cpu.switch_to(pd().mmu_regs);
kernel_to_user_context_switch((static_cast<Cpu::Context*>(&*regs)), kernel_to_user_context_switch((static_cast<Cpu::Context*>(&*regs)),
(void*)cpu.stack_start()); (void*)cpu.stack_start());
} }

View File

@@ -22,24 +22,34 @@
.global hypervisor_exception_vector .global hypervisor_exception_vector
hypervisor_exception_vector: hypervisor_exception_vector:
.rept 16 .rept 16
add sp, sp, #-16 /* push x0, x1 to stack */ add sp, sp, #-16 /* push x29, x30 to stack */
stp x0, x1, [sp] stp x29, x30, [sp]
mrs x1, hcr_el2 /* read HCR register */ mrs x30, hcr_el2 /* read HCR register */
tst x1, #1 /* check VM bit */ tst x30, #1 /* check VM bit */
beq _host_to_vm /* if VM bit is not set, switch to VM */ beq _from_host /* if VM bit is not set, its a host call */
ldr x0, [sp, #32] /* otherwise, load vm_state pointer */ ldr x29, [sp, #32] /* otherwise, load vm_state pointer */
adr x1, . /* hold exception vector offset in x1 */ adr x30, . /* hold exception vector offset in x30 */
and x1, x1, #0xf80 and x30, x30, #0xf80
b _vm_to_host b _from_vm
.balign 128 .balign 128
.endr .endr
_host_to_vm: _from_host:
ldp x29, x30, [sp], #2*8 /* pop x29, x30 from stack */
cmp x0, #0
beq _to_vm
cmp x0, #1
beq _invalidate_tlb
eret
add sp, sp, #-16 /* push arg2 (vm pic state) to stack */ _to_vm:
str x2, [sp] add sp, sp, #-16 /* push arg1/2 (vm/host state to stack */
stp x1, x2, [sp]
add sp, sp, #-16 /* push arg3 (vm pic state) to stack */
str x3, [sp]
msr vttbr_el2, x3 /* stage2 table pointer was arg3 */ msr vttbr_el2, x4 /* stage2 table pointer was arg4 */
mov x0, x1
add x0, x0, #31*8 /* skip x0...x30, loaded later */ add x0, x0, #31*8 /* skip x0...x30, loaded later */
@@ -179,27 +189,38 @@ _host_to_vm:
eret eret
_vm_to_host:
_invalidate_tlb:
msr vttbr_el2, x1
tlbi vmalle1is
msr vttbr_el2, xzr
eret
_from_vm:
/********************* /*********************
** Save vm context ** ** Save vm context **
*********************/ *********************/
/** general-purpose register **/ /** general-purpose register **/
add x0, x0, #2*8 /* skip x0 and x1 for now */ stp x0, x1, [x29], #2*8
stp x2, x3, [x0], #2*8 stp x2, x3, [x29], #2*8
stp x4, x5, [x0], #2*8 stp x4, x5, [x29], #2*8
stp x6, x7, [x0], #2*8 stp x6, x7, [x29], #2*8
stp x8, x9, [x0], #2*8 stp x8, x9, [x29], #2*8
stp x10, x11, [x0], #2*8 stp x10, x11, [x29], #2*8
stp x12, x13, [x0], #2*8 stp x12, x13, [x29], #2*8
stp x14, x15, [x0], #2*8 stp x14, x15, [x29], #2*8
stp x16, x17, [x0], #2*8 stp x16, x17, [x29], #2*8
stp x18, x19, [x0], #2*8 stp x18, x19, [x29], #2*8
stp x20, x21, [x0], #2*8 stp x20, x21, [x29], #2*8
stp x22, x23, [x0], #2*8 stp x22, x23, [x29], #2*8
stp x24, x25, [x0], #2*8 stp x24, x25, [x29], #2*8
stp x26, x27, [x0], #2*8 stp x26, x27, [x29], #2*8
mov x0, x29
mov x1, x30
ldp x29, x30, [sp], #2*8 /* pop x29, x30 from stack */
stp x28, x29, [x0], #2*8 stp x28, x29, [x0], #2*8
str x30, [x0], #1*8 str x30, [x0], #1*8
@@ -284,11 +305,8 @@ _vm_to_host:
mov x0, #0b111 mov x0, #0b111
msr cnthctl_el2, x0 msr cnthctl_el2, x0
ldr x29, [sp], #2*8 /* pop vm pic state from stack */
ldp x0, x1, [sp], #2*8 /* pop x0, x1 from stack */ ldp x2, x30, [sp], #2*8 /* pop vm, and host state from stack */
ldr x29, [sp], #2*8 /* pop vm pic state from stack */
ldp x2, x30, [sp], #2*8 /* pop vm, and host state from stack */
stp x0, x1, [x2] /* save x0, x1 to vm state */
/********************** /**********************
@@ -364,6 +382,7 @@ _vm_to_host:
eret eret
/* host kernel must jump to this point to switch to a vm */ /* host kernel must jump to this point to switch to a vm */
.global hypervisor_enter_vm .global hypervisor_call
hypervisor_enter_vm: hypervisor_call:
hvc #0 hvc #0
ret

View File

@@ -0,0 +1,52 @@
/*
* \brief Interface between kernel and hypervisor
* \author Stefan Kalkowski
* \date 2022-06-13
*/
/*
* Copyright (C) 2022 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _SPEC__ARM_V8__VIRTUALIZATION_HYPERVISOR_H_
#define _SPEC__ARM_V8__VIRTUALIZATION_HYPERVISOR_H_
#include <base/stdint.h>
namespace Hypervisor {
enum Call_number {
WORLD_SWITCH = 0,
TLB_INVALIDATE = 1,
};
using Call_arg = Genode::umword_t;
using Call_ret = Genode::umword_t;
extern "C"
Call_ret hypervisor_call(Call_arg call_id,
Call_arg arg0,
Call_arg arg1,
Call_arg arg2,
Call_arg arg3);
inline void invalidate_tlb(Call_arg ttbr)
{
hypervisor_call(TLB_INVALIDATE, ttbr, 0, 0, 0);
}
inline void switch_world(Call_arg guest_state,
Call_arg host_state,
Call_arg pic_state,
Call_arg ttbr)
{
hypervisor_call(WORLD_SWITCH, guest_state, host_state, pic_state, ttbr);
}
}
#endif /* _SPEC__ARM_V8__VIRTUALIZATION_HYPERVISOR_H_ */

View File

@@ -23,14 +23,12 @@
#include <kernel/vm.h> #include <kernel/vm.h>
#include <kernel/main.h> #include <kernel/main.h>
#include <spec/arm_v8/virtualization/hypervisor.h>
using Genode::addr_t; using Genode::addr_t;
using Kernel::Cpu; using Kernel::Cpu;
using Kernel::Vm; using Kernel::Vm;
extern void * kernel_stack;
extern "C" void hypervisor_enter_vm(addr_t vm, addr_t host,
addr_t pic, addr_t guest_table);
static Genode::Vm_state & host_context(Cpu & cpu) static Genode::Vm_state & host_context(Cpu & cpu)
{ {
@@ -154,6 +152,15 @@ Vm::Vm(Irq::Pool & user_irq_pool,
} }
Vm::~Vm()
{
Cpu::Vttbr_el2::access_t vttbr_el2 =
Cpu::Vttbr_el2::Ba::masked((Cpu::Vttbr_el2::access_t)_id.table);
Cpu::Vttbr_el2::Asid::set(vttbr_el2, _id.id);
Hypervisor::invalidate_tlb(vttbr_el2);
}
void Vm::exception(Cpu & cpu) void Vm::exception(Cpu & cpu)
{ {
switch (_state.exception_type) { switch (_state.exception_type) {
@@ -197,7 +204,7 @@ void Vm::proceed(Cpu & cpu)
addr_t pic = Hw::Mm::el2_addr(&_vcpu_context.pic); addr_t pic = Hw::Mm::el2_addr(&_vcpu_context.pic);
addr_t host = Hw::Mm::el2_addr(&host_context(cpu)); addr_t host = Hw::Mm::el2_addr(&host_context(cpu));
hypervisor_enter_vm(guest, host, pic, vttbr_el2); Hypervisor::switch_world(guest, host, pic, vttbr_el2);
} }

View File

@@ -115,11 +115,14 @@ class Genode::Cpu : public Arm_v7_cpu
*/ */
static unsigned executing_id() { return Mpidr::Aff_0::get(Mpidr::read()); } static unsigned executing_id() { return Mpidr::Aff_0::get(Mpidr::read()); }
bool active(Mmu_context & mmu_context)
void switch_to(Context &, Mmu_context & mmu_context)
{ {
if (mmu_context.id() && (Ttbr0_64bit::read() != mmu_context.ttbr0)) return (Ttbr0_64bit::read() == mmu_context.ttbr0);
Ttbr0_64bit::write(mmu_context.ttbr0); }
void switch_to(Mmu_context & mmu_context)
{
Ttbr0_64bit::write(mmu_context.ttbr0);
} }
}; };

View File

@@ -53,22 +53,16 @@ Mmu_context::~Mmu_context()
} }
bool Genode::Cpu::active(Mmu_context & context)
{
return Satp::read() == context.satp;
}
void Genode::Cpu::switch_to(Mmu_context & context) void Genode::Cpu::switch_to(Mmu_context & context)
{ {
/* Satp::write(context.satp);
* The sstatus register defines to which privilege level sfence();
* the machin returns when doing an exception return
*/
bool user = Satp::Asid::get(context.satp);
Sstatus::access_t v = Sstatus::read();
Sstatus::Spp::set(v, user ? 0 : 1);
Sstatus::write(v);
/* change the translation table when necessary */
if (user) {
Satp::write(context.satp);
sfence();
}
} }

View File

@@ -97,6 +97,7 @@ class Genode::Cpu : public Hw::Riscv_cpu
static void invalidate_tlb_by_pid(unsigned const /* pid */) { sfence(); } static void invalidate_tlb_by_pid(unsigned const /* pid */) { sfence(); }
bool active(Mmu_context & context);
void switch_to(Mmu_context & context); void switch_to(Mmu_context & context);
static void mmu_fault(Context & c, Kernel::Thread_fault & f); static void mmu_fault(Context & c, Kernel::Thread_fault & f);

View File

@@ -100,7 +100,16 @@ void Kernel::Thread::_call_cache_invalidate_data_region() { }
void Kernel::Thread::proceed(Cpu & cpu) void Kernel::Thread::proceed(Cpu & cpu)
{ {
cpu.switch_to(_pd->mmu_regs); /*
* The sstatus register defines to which privilege level
* the machine returns when doing an exception return
*/
Cpu::Sstatus::access_t v = Cpu::Sstatus::read();
Cpu::Sstatus::Spp::set(v, (type() == USER) ? 0 : 1);
Cpu::Sstatus::write(v);
if (!cpu.active(pd().mmu_regs) && type() != CORE)
cpu.switch_to(_pd->mmu_regs);
asm volatile("csrw sscratch, %1 \n" asm volatile("csrw sscratch, %1 \n"
"mv x31, %0 \n" "mv x31, %0 \n"

View File

@@ -111,11 +111,20 @@ extern void const * const kernel_stack;
extern Genode::size_t const kernel_stack_size; extern Genode::size_t const kernel_stack_size;
void Genode::Cpu::switch_to(Context & context, Mmu_context &mmu_context) bool Genode::Cpu::active(Mmu_context &mmu_context)
{ {
if ((context.cs != 0x8) && (mmu_context.cr3 != Cr3::read())) return (mmu_context.cr3 == Cr3::read());
Cr3::write(mmu_context.cr3); }
void Genode::Cpu::switch_to(Mmu_context &mmu_context)
{
Cr3::write(mmu_context.cr3);
}
void Genode::Cpu::switch_to(Context & context)
{
tss.ist[0] = (addr_t)&context + sizeof(Genode::Cpu_state); tss.ist[0] = (addr_t)&context + sizeof(Genode::Cpu_state);
addr_t const stack_base = reinterpret_cast<addr_t>(&kernel_stack); addr_t const stack_base = reinterpret_cast<addr_t>(&kernel_stack);

View File

@@ -126,7 +126,10 @@ class Genode::Cpu : public Hw::X86_64_cpu
* *
* \param context next CPU context * \param context next CPU context
*/ */
void switch_to(Context & context, Mmu_context &mmu_context); void switch_to(Context & context);
bool active(Mmu_context &mmu_context);
void switch_to(Mmu_context &mmu_context);
static void mmu_fault(Context & regs, Kernel::Thread_fault & fault); static void mmu_fault(Context & regs, Kernel::Thread_fault & fault);

View File

@@ -43,7 +43,10 @@ void Kernel::Thread::_call_cache_invalidate_data_region() { }
void Kernel::Thread::proceed(Cpu & cpu) void Kernel::Thread::proceed(Cpu & cpu)
{ {
cpu.switch_to(*regs, pd().mmu_regs); if (!cpu.active(pd().mmu_regs) && type() != CORE)
cpu.switch_to(pd().mmu_regs);
cpu.switch_to(*regs);
asm volatile("fxrstor (%1) \n" asm volatile("fxrstor (%1) \n"
"mov %0, %%rsp \n" "mov %0, %%rsp \n"

View File

@@ -1,2 +0,0 @@
TARGET = ld-hw
LIBS = ld-hw

View File

@@ -5,7 +5,7 @@
*/ */
/* /*
* Copyright (C) 2014-2017 Genode Labs GmbH * Copyright (C) 2014-2022 Genode Labs GmbH
* *
* This file is part of the Genode OS framework, which is distributed * This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3. * under the terms of the GNU Affero General Public License version 3.
@@ -18,125 +18,110 @@
/* core includes */ /* core includes */
#include <kernel/cpu_scheduler.h> #include <kernel/cpu_scheduler.h>
/* using namespace Genode;
* Utilities using namespace Kernel;
*/
using Genode::size_t; struct Main
using Genode::addr_t;
using Genode::construct_at;
using Kernel::Cpu_share;
using Kernel::Cpu_scheduler;
struct Data
{ {
Cpu_share idle; enum { MAX_SHARES = 10 };
Cpu_scheduler scheduler;
char shares[9][sizeof(Cpu_share)];
Data() : idle(0, 0), scheduler(idle, 1000, 100) { } Constructible<Cpu_share> shares[MAX_SHARES] {};
Cpu_scheduler scheduler;
time_t current_time { 0 };
Cpu_share & _idle()
{
if (!shares[0].constructed()) shares[0].construct(0, 0);
return *shares[0];
}
Main() : scheduler(_idle(), 1000, 100) { }
void done()
{
Genode::log("done");
while (1) ;
}
unsigned share_id(Cpu_share & share)
{
for (unsigned i = 0; i < MAX_SHARES; i++)
if (shares[i].constructed() && (&*shares[i] == &share))
return i;
return ~0U;
}
Cpu_share & share(unsigned const id)
{
return *shares[id];
}
void create(unsigned const id)
{
switch (id) {
case 1: shares[id].construct(2, 230); break;
case 2: shares[id].construct(0, 170); break;
case 3: shares[id].construct(3, 110); break;
case 4: shares[id].construct(1, 90); break;
case 5: shares[id].construct(3, 120); break;
case 6: shares[id].construct(3, 0); break;
case 7: shares[id].construct(2, 180); break;
case 8: shares[id].construct(2, 100); break;
case 9: shares[id].construct(2, 0); break;
default: return;
}
scheduler.insert(*shares[id]);
}
void destroy(unsigned const id)
{
if (!id || id >= MAX_SHARES)
return;
scheduler.remove(share(id));
shares[id].destruct();
}
unsigned time()
{
return scheduler.quota() - scheduler.residual();
}
void update_check(unsigned const l, unsigned const c, unsigned const t,
unsigned const s, unsigned const q)
{
current_time += c;
scheduler.update(current_time);
unsigned const st = time();
if (t != st) {
log("wrong time ", st, " in line ", l);
done();
}
Cpu_share & hs = scheduler.head();
unsigned const hq = scheduler.head_quota();
if (&hs != &share(s)) {
log("wrong share ", share_id(hs), " in line ", l);
done();
}
if (hq != q) {
log("wrong quota ", hq, " in line ", l);
done();
}
}
void ready_check(unsigned const l, unsigned const s, bool const x)
{
scheduler.ready(share(s));
if (scheduler.need_to_schedule() != x) {
log("wrong check result ", scheduler.need_to_schedule(), " in line ", l);
done();
}
}
void test();
}; };
Data * data()
{
static Data d;
return &d;
}
void done()
{
Genode::log("done");
while (1) ;
}
unsigned share_id(void * const pointer)
{
addr_t const address = (addr_t)pointer;
addr_t const base = (addr_t)data()->shares;
if (address < base || address >= base + sizeof(data()->shares)) {
return 0; }
return (unsigned)((address - base) / sizeof(Cpu_share) + 1);
}
Cpu_share * share(unsigned const id)
{
if (!id) { return &data()->idle; }
return reinterpret_cast<Cpu_share *>(&data()->shares[id - 1]);
}
void create(unsigned const id)
{
Cpu_share * const s = share(id);
void * const p = (void *)s;
switch (id) {
case 1: construct_at<Cpu_share>(p, 2, 230); break;
case 2: construct_at<Cpu_share>(p, 0, 170); break;
case 3: construct_at<Cpu_share>(p, 3, 110); break;
case 4: construct_at<Cpu_share>(p, 1, 90); break;
case 5: construct_at<Cpu_share>(p, 3, 120); break;
case 6: construct_at<Cpu_share>(p, 3, 0); break;
case 7: construct_at<Cpu_share>(p, 2, 180); break;
case 8: construct_at<Cpu_share>(p, 2, 100); break;
case 9: construct_at<Cpu_share>(p, 2, 0); break;
default: return;
}
data()->scheduler.insert(*s);
}
void destroy(unsigned const id)
{
Cpu_share * const s = share(id);
data()->scheduler.remove(*s);
s->~Cpu_share();
}
unsigned time()
{
return data()->scheduler.quota() -
data()->scheduler.residual();
}
void update_check(unsigned const l, unsigned const c, unsigned const t,
unsigned const s, unsigned const q)
{
data()->scheduler.update(c);
unsigned const st = time();
if (t != st) {
Genode::log("wrong time ", st, " in line ", l);
done();
}
Cpu_share &hs = data()->scheduler.head();
unsigned const hq = data()->scheduler.head_quota();
if (&hs != share(s)) {
unsigned const hi = share_id(&hs);
Genode::log("wrong share ", hi, " in line ", l);
done();
}
if (hq != q) {
Genode::log("wrong quota ", hq, " in line ", l);
done();
}
}
void ready_check(unsigned const l, unsigned const s, bool const x)
{
data()->scheduler.ready_check(*share(s));
if (data()->scheduler.need_to_schedule() != x) {
Genode::log("wrong check result ", data()->scheduler.need_to_schedule(), " in line ", l);
done();
}
}
/* /*
* Shortcuts for all basic operations that the test consists of * Shortcuts for all basic operations that the test consists of
@@ -144,10 +129,10 @@ void ready_check(unsigned const l, unsigned const s, bool const x)
#define C(s) create(s); #define C(s) create(s);
#define D(s) destroy(s); #define D(s) destroy(s);
#define A(s) data()->scheduler.ready(*share(s)); #define A(s) scheduler.ready(share(s));
#define I(s) data()->scheduler.unready(*share(s)); #define I(s) scheduler.unready(share(s));
#define Y data()->scheduler.yield(); #define Y scheduler.yield();
#define Q(s, q) data()->scheduler.quota(*share(s), q); #define Q(s, q) scheduler.quota(share(s), q);
#define U(c, t, s, q) update_check(__LINE__, c, t, s, q); #define U(c, t, s, q) update_check(__LINE__, c, t, s, q);
#define O(s) ready_check(__LINE__, s, true); #define O(s) ready_check(__LINE__, s, true);
#define N(s) ready_check(__LINE__, s, false); #define N(s) ready_check(__LINE__, s, false);
@@ -157,6 +142,13 @@ void ready_check(unsigned const l, unsigned const s, bool const x)
* Main routine * Main routine
*/ */
void Component::construct(Genode::Env &) void Component::construct(Genode::Env &)
{
static Main main;
main.test();
}
void Main::test()
{ {
/* /*
* Step-by-step testing * Step-by-step testing

View File

@@ -1,8 +1,7 @@
FROM_BASE_LINUX := etc src/lib/syscall src/lib/lx_hybrid lib/import include FROM_BASE_LINUX := etc src/lib/syscall src/lib/lx_hybrid lib/import include
FROM_BASE_LINUX_AND_BASE := lib/mk src/lib/base src/include FROM_BASE_LINUX_AND_BASE := src/lib/base src/include
FROM_BASE := src/lib/timeout
content: $(FROM_BASE_LINUX) $(FROM_BASE_LINUX_AND_BASE) $(FROM_BASE) LICENSE content: $(FROM_BASE_LINUX) $(FROM_BASE_LINUX_AND_BASE) LICENSE
$(FROM_BASE_LINUX): $(FROM_BASE_LINUX):
mkdir -p $@ mkdir -p $@
@@ -13,9 +12,30 @@ $(FROM_BASE_LINUX_AND_BASE):
cp -r $(GENODE_DIR)/repos/base/$@/* $@ cp -r $(GENODE_DIR)/repos/base/$@/* $@
cp -r $(REP_DIR)/$@/* $@ cp -r $(REP_DIR)/$@/* $@
$(FROM_BASE): BASE_LIB_MK_CONTENT := \
$(addprefix lib/mk/,base-common.inc timeout.mk)
content: $(BASE_LIB_MK_CONTENT)
$(BASE_LIB_MK_CONTENT):
mkdir -p $(dir $@)
cp $(GENODE_DIR)/repos/base/$@ $@
content: src/lib/timeout
src/lib/timeout:
mkdir -p $@ mkdir -p $@
cp -r $(GENODE_DIR)/repos/base/$@/* $@ cp -r $(GENODE_DIR)/repos/base/$@/* $@
BASE_LINUX_LIB_MK_CONTENT := \
$(addprefix lib/mk/,lx_hybrid.mk base-linux.inc base-linux-common.mk) \
$(foreach S,arm arm_64 x86_32 x86_64,lib/mk/spec/$S/syscall-linux.mk)
content: $(BASE_LINUX_LIB_MK_CONTENT)
$(BASE_LINUX_LIB_MK_CONTENT):
mkdir -p $(dir $@)
cp $(REP_DIR)/$@ $@
LICENSE: LICENSE:
cp $(GENODE_DIR)/LICENSE $@ cp $(GENODE_DIR)/LICENSE $@

View File

@@ -1 +1 @@
2022-04-12 dcb2c9200b333adb17f9a8737620cbd84f641408 2022-10-11 4544924c73b2ee1d8d2717672320f14732807267

View File

@@ -10,7 +10,6 @@ content:
mv lib/mk/spec/$$spec/ld-linux.mk lib/mk/spec/$$spec/ld.mk; done; mv lib/mk/spec/$$spec/ld-linux.mk lib/mk/spec/$$spec/ld.mk; done;
sed -i "/TARGET/s/core-linux/core/" src/core/linux/target.mk sed -i "/TARGET/s/core-linux/core/" src/core/linux/target.mk
sed -i "s/BOARD.*unknown/BOARD := linux/" lib/mk/core-linux.inc sed -i "s/BOARD.*unknown/BOARD := linux/" lib/mk/core-linux.inc
sed -i "s/ld-linux/ld/" src/lib/ld/linux/target.mk
sed -i "s/linux_timer_drv/timer/" src/timer/linux/target.mk sed -i "s/linux_timer_drv/timer/" src/timer/linux/target.mk
rm -rf src/lib/initramfs rm -rf src/initramfs

View File

@@ -1 +1 @@
2022-05-24 4aea382035415c79bf5d551642ebfa64d42e4d21 2022-10-11 d7e12d81f12f081bb7c00233c18f3c8ac2f00d67

View File

@@ -5,14 +5,14 @@ REQUIRES = x86_64
INITRAMFS = initramfs INITRAMFS = initramfs
INITRAMFS_SRC_C = init.c INITRAMFS_SRC_C = init.c
EXT_OBJECTS += $(BUILD_BASE_DIR)/lib/initramfs/$(INITRAMFS) EXT_OBJECTS += $(BUILD_BASE_DIR)/initramfs/$(INITRAMFS)
$(TARGET): $(INITRAMFS) $(TARGET): $(INITRAMFS)
$(INITRAMFS): $(INITRAMFS_SRC_C) $(INITRAMFS): $(INITRAMFS_SRC_C)
$(MSG_BUILD)$(INITRAMFS) $(MSG_BUILD)$(INITRAMFS)
$(VERBOSE)gcc $^ -O0 $(CC_MARCH) -Wall -W -Wextra -Werror -std=gnu99 -o $@ -Wl,-O3 -Wl,--as-needed -static $(VERBOSE)gcc $^ -O0 $(CC_MARCH) -Wall -W -Wextra -Werror -std=gnu99 -o $@ -Wl,-O3 -Wl,--as-needed -static
$(VERBOSE)ln -sf $(BUILD_BASE_DIR)/lib/initramfs/$(INITRAMFS) $(BUILD_BASE_DIR)/bin/ $(VERBOSE)ln -sf $(BUILD_BASE_DIR)/initramfs/$(INITRAMFS) $(BUILD_BASE_DIR)/bin/
clean_initramfs: clean_initramfs:
$(VERBOSE)rm -rf $(INITRAMFS) $(VERBOSE)rm -rf $(INITRAMFS)

View File

@@ -172,6 +172,24 @@ void Region_map_mmap::_add_to_rmap(Region const &region)
} }
/*
* Tracing must be inhibited in attach/detach as RPC trace points may trigger
* attachment of trace dataspaces, which would result in nested mutex
* acquisition.
*/
namespace Genode { extern bool inhibit_tracing; }
struct Inhibit_tracing_guard
{
bool old_value = inhibit_tracing;
Inhibit_tracing_guard() { inhibit_tracing = true; }
~Inhibit_tracing_guard() { inhibit_tracing = old_value; }
};
Region_map::Local_addr Region_map_mmap::attach(Dataspace_capability ds, Region_map::Local_addr Region_map_mmap::attach(Dataspace_capability ds,
size_t size, off_t offset, size_t size, off_t offset,
bool use_local_addr, bool use_local_addr,
@@ -180,6 +198,8 @@ Region_map::Local_addr Region_map_mmap::attach(Dataspace_capability ds,
{ {
Mutex::Guard mutex_guard(mutex()); Mutex::Guard mutex_guard(mutex());
Inhibit_tracing_guard it_guard { };
/* only support attach_at for sub RM sessions */ /* only support attach_at for sub RM sessions */
if (_sub_rm && !use_local_addr) { if (_sub_rm && !use_local_addr) {
error("Region_map_mmap::attach: attaching w/o local addr not supported"); error("Region_map_mmap::attach: attaching w/o local addr not supported");
@@ -325,6 +345,8 @@ void Region_map_mmap::detach(Region_map::Local_addr local_addr)
{ {
Mutex::Guard mutex_guard(mutex()); Mutex::Guard mutex_guard(mutex());
Inhibit_tracing_guard it_guard { };
/* /*
* Cases * Cases
* *

View File

@@ -1,4 +0,0 @@
TARGET = ld-linux.lib
LIBS = ld-linux
BUILD_ARTIFACTS := ld-linux.lib.so

View File

@@ -260,6 +260,30 @@ namespace Nova {
*/ */
enum Pd_op { TRANSFER_QUOTA = 0U, PD_DEBUG = 2U }; enum Pd_op { TRANSFER_QUOTA = 0U, PD_DEBUG = 2U };
class Gsi_flags
{
private:
uint8_t _value { 0 };
public:
enum Mode { HIGH, LOW, EDGE };
Gsi_flags() { }
Gsi_flags(Mode m)
{
switch (m) {
case HIGH: _value = 0b110; break; /* level-high */
case LOW: _value = 0b111; break; /* level-low */
case EDGE: _value = 0b100; break; /* edge-triggered */
}
}
uint8_t value() const { return _value; }
};
class Descriptor class Descriptor
{ {

View File

@@ -457,12 +457,12 @@ namespace Nova {
ALWAYS_INLINE ALWAYS_INLINE
inline uint8_t assign_gsi(mword_t sm, mword_t dev, mword_t cpu, inline uint8_t assign_gsi(mword_t sm, mword_t dev, mword_t cpu,
mword_t &msi_addr, mword_t &msi_data, mword_t &msi_addr, mword_t &msi_data,
mword_t si = ~0UL) mword_t si = ~0UL, Gsi_flags flags = Gsi_flags())
{ {
msi_addr = dev; msi_addr = dev;
msi_data = cpu; msi_data = cpu;
return syscall_5(NOVA_ASSIGN_GSI, 0, sm, msi_addr, msi_data, si); return syscall_5(NOVA_ASSIGN_GSI, flags.value(), sm, msi_addr, msi_data, si);
} }

View File

@@ -402,11 +402,11 @@ namespace Nova {
ALWAYS_INLINE ALWAYS_INLINE
inline uint8_t assign_gsi(mword_t sm, mword_t dev, mword_t cpu, inline uint8_t assign_gsi(mword_t sm, mword_t dev, mword_t cpu,
mword_t &msi_addr, mword_t &msi_data, mword_t &msi_addr, mword_t &msi_data,
mword_t si = ~0UL) mword_t si = ~0UL, Gsi_flags flags = Gsi_flags())
{ {
msi_addr = dev; msi_addr = dev;
msi_data = cpu; msi_data = cpu;
return syscall_5(NOVA_ASSIGN_GSI, 0, sm, msi_addr, msi_data, si); return syscall_5(NOVA_ASSIGN_GSI, flags.value(), sm, msi_addr, msi_data, si);
} }
} }
#endif /* _INCLUDE__SPEC__64BIT__NOVA__SYSCALLS_H_ */ #endif /* _INCLUDE__SPEC__64BIT__NOVA__SYSCALLS_H_ */

View File

@@ -1 +1 @@
33a2fa953ec52b0f63b921f4d33d68891c0aada0 9ad770935115d201863fd83924e4684b14b8b56f

View File

@@ -4,7 +4,7 @@ DOWNLOADS := nova.git
# r10 branch # r10 branch
URL(nova) := https://github.com/alex-ab/NOVA.git URL(nova) := https://github.com/alex-ab/NOVA.git
REV(nova) := 00dc49bc18e7f72a9c85487e8f94fd859511d89d REV(nova) := a34076e7b8d48d08c2edee7754eadad8b6ea5312
DIR(nova) := src/kernel/nova DIR(nova) := src/kernel/nova
PATCHES := $(sort $(wildcard $(REP_DIR)/patches/*.patch)) PATCHES := $(sort $(wildcard $(REP_DIR)/patches/*.patch))

View File

@@ -1,5 +1,8 @@
FROM_BASE_NOVA := etc include FROM_BASE_NOVA := etc include
FROM_BASE := lib/mk/timeout.mk src/lib/timeout
# base-nova.lib.a depends on timeout.lib.a, which includes base/internal/gloabls.h
FROM_BASE := lib/mk/timeout.mk src/lib/timeout \
src/include/base/internal/globals.h
content: $(FROM_BASE_NOVA) $(FROM_BASE) LICENSE content: $(FROM_BASE_NOVA) $(FROM_BASE) LICENSE

View File

@@ -1 +1 @@
2022-05-24 91bc8d51bbe703d56f5671019d14e4636f21bf1f 2022-10-11 4458ea63a69ae070e19a3cb09a403137755d2cb0

View File

@@ -15,8 +15,7 @@ src/kernel/nova: src/kernel
content: content:
for spec in x86_32 x86_64; do \ for spec in x86_32 x86_64; do \
mv lib/mk/spec/$$spec/ld-nova.mk lib/mk/spec/$$spec/ld.mk; \ mv lib/mk/spec/$$spec/ld-nova.mk lib/mk/spec/$$spec/ld.mk; \
done; done;
sed -i "s/ld-nova/ld/" src/lib/ld/nova/target.mk
sed -i "s/nova_timer_drv/timer/" src/timer/nova/target.mk sed -i "s/nova_timer_drv/timer/" src/timer/nova/target.mk

View File

@@ -1 +1 @@
2022-05-24 8b59a28ade1392bae4aa772bbead1584a2dde1de 2022-10-11 574204b7d442811236bba60e4fe3f79e34fe9985

View File

@@ -13,7 +13,9 @@
#ifndef _CORE__INCLUDE__IRQ_OBJECT_H_ #ifndef _CORE__INCLUDE__IRQ_OBJECT_H_
#define _CORE__INCLUDE__IRQ_OBJECT_H_ #define _CORE__INCLUDE__IRQ_OBJECT_H_
namespace Genode { class Irq_object; } #include <nova/syscall-generic.h> /* Gsi_flags */
namespace Genode { class Irq_object; class Irq_args; }
class Genode::Irq_object class Genode::Irq_object
{ {
@@ -26,22 +28,24 @@ class Genode::Irq_object
addr_t _msi_data; addr_t _msi_data;
addr_t _device_phys = 0; /* PCI config extended address */ addr_t _device_phys = 0; /* PCI config extended address */
Nova::Gsi_flags _gsi_flags { };
enum { KERNEL_CAP_COUNT_LOG2 = 0 }; enum { KERNEL_CAP_COUNT_LOG2 = 0 };
Genode::addr_t irq_sel() const { return _kernel_caps; } addr_t irq_sel() const { return _kernel_caps; }
public: public:
Irq_object(); Irq_object();
~Irq_object(); ~Irq_object();
Genode::addr_t msi_address() const { return _msi_addr; } addr_t msi_address() const { return _msi_addr; }
Genode::addr_t msi_value() const { return _msi_data; } addr_t msi_value() const { return _msi_data; }
void sigh(Signal_context_capability cap); void sigh(Signal_context_capability cap);
void ack_irq(); void ack_irq();
void start(unsigned irq, Genode::addr_t); void start(unsigned irq, addr_t, Irq_args const &);
}; };
#endif /* _CORE__INCLUDE__IRQ_OBJECT_H_ */ #endif /* _CORE__INCLUDE__IRQ_OBJECT_H_ */

View File

@@ -18,6 +18,7 @@
/* core includes */ /* core includes */
#include <irq_root.h> #include <irq_root.h>
#include <irq_args.h>
#include <platform.h> #include <platform.h>
/* NOVA includes */ /* NOVA includes */
@@ -27,13 +28,12 @@
using namespace Genode; using namespace Genode;
static bool irq_ctrl(Genode::addr_t irq_sel, static bool irq_ctrl(addr_t irq_sel, addr_t &msi_addr, addr_t &msi_data,
Genode::addr_t &msi_addr, Genode::addr_t &msi_data, addr_t sig_sel, Nova::Gsi_flags flags, addr_t virt_addr)
Genode::addr_t sig_sel, Genode::addr_t virt_addr = 0)
{ {
/* assign IRQ to CPU && request msi data to be used by driver */ /* assign IRQ to CPU && request msi data to be used by driver */
uint8_t res = Nova::assign_gsi(irq_sel, virt_addr, boot_cpu(), uint8_t res = Nova::assign_gsi(irq_sel, virt_addr, boot_cpu(),
msi_addr, msi_data, sig_sel); msi_addr, msi_data, sig_sel, flags);
if (res != Nova::NOVA_OK) if (res != Nova::NOVA_OK)
error("setting up MSI failed - error ", res); error("setting up MSI failed - error ", res);
@@ -46,30 +46,28 @@ static bool irq_ctrl(Genode::addr_t irq_sel,
} }
static bool associate(Genode::addr_t irq_sel, static bool associate_gsi(addr_t irq_sel, Signal_context_capability sig_cap,
Genode::addr_t &msi_addr, Genode::addr_t &msi_data, Nova::Gsi_flags gsi_flags)
Genode::Signal_context_capability sig_cap,
Genode::addr_t virt_addr = 0)
{
return irq_ctrl(irq_sel, msi_addr, msi_data, sig_cap.local_name(),
virt_addr);
}
static void deassociate(Genode::addr_t irq_sel)
{ {
addr_t dummy1 = 0, dummy2 = 0; addr_t dummy1 = 0, dummy2 = 0;
if (!irq_ctrl(irq_sel, dummy1, dummy2, irq_sel)) return irq_ctrl(irq_sel, dummy1, dummy2, sig_cap.local_name(), gsi_flags, 0);
}
static void deassociate(addr_t irq_sel)
{
addr_t dummy1 = 0, dummy2 = 0;
if (!irq_ctrl(irq_sel, dummy1, dummy2, irq_sel, Nova::Gsi_flags(), 0))
warning("Irq could not be de-associated"); warning("Irq could not be de-associated");
} }
static bool msi(Genode::addr_t irq_sel, Genode::addr_t phys_mem, static bool associate_msi(addr_t irq_sel, addr_t phys_mem, addr_t &msi_addr,
Genode::addr_t &msi_addr, Genode::addr_t &msi_data, addr_t &msi_data, Signal_context_capability sig_cap)
Genode::Signal_context_capability sig_cap)
{ {
return platform().region_alloc().alloc_aligned(4096, 12).convert<bool>( return platform().region_alloc().alloc_aligned(4096, 12).convert<bool>(
[&] (void *virt_ptr) { [&] (void *virt_ptr) {
@@ -89,7 +87,7 @@ static bool msi(Genode::addr_t irq_sel, Genode::addr_t phys_mem,
} }
/* try to assign MSI to device */ /* try to assign MSI to device */
bool res = associate(irq_sel, msi_addr, msi_data, sig_cap, virt_addr); bool res = irq_ctrl(irq_sel, msi_addr, msi_data, sig_cap.local_name(), Nova::Gsi_flags(), virt_addr);
unmap_local(Nova::Mem_crd(virt_addr >> 12, 0, Rights(true, true, true))); unmap_local(Nova::Mem_crd(virt_addr >> 12, 0, Rights(true, true, true)));
platform().region_alloc().free(virt_ptr, 4096); platform().region_alloc().free(virt_ptr, 4096);
@@ -118,11 +116,12 @@ void Irq_object::sigh(Signal_context_capability cap)
return; return;
} }
/* associate GSI or MSI to device belonging to device_phys */
bool ok = false; bool ok = false;
if (_device_phys) if (_device_phys)
ok = msi(irq_sel(), _device_phys, _msi_addr, _msi_data, cap); ok = associate_msi(irq_sel(), _device_phys, _msi_addr, _msi_data, cap);
else else
ok = associate(irq_sel(), _msi_addr, _msi_data, cap); ok = associate_gsi(irq_sel(), cap, _gsi_flags);
if (!ok) { if (!ok) {
deassociate(irq_sel()); deassociate(irq_sel());
@@ -141,7 +140,7 @@ void Irq_object::ack_irq()
} }
void Irq_object::start(unsigned irq, Genode::addr_t const device_phys) void Irq_object::start(unsigned irq, addr_t const device_phys, Irq_args const &irq_args)
{ {
/* map IRQ SM cap from kernel to core at irq_sel selector */ /* map IRQ SM cap from kernel to core at irq_sel selector */
using Nova::Obj_crd; using Nova::Obj_crd;
@@ -158,12 +157,29 @@ void Irq_object::start(unsigned irq, Genode::addr_t const device_phys)
throw Service_denied(); throw Service_denied();
} }
/* initialize GSI IRQ flags */
auto gsi_flags = [] (Irq_args const &args) {
if (args.trigger() == Irq_session::TRIGGER_UNCHANGED
|| args.polarity() == Irq_session::POLARITY_UNCHANGED)
return Nova::Gsi_flags();
if (args.trigger() == Irq_session::TRIGGER_EDGE)
return Nova::Gsi_flags(Nova::Gsi_flags::EDGE);
if (args.polarity() == Irq_session::POLARITY_HIGH)
return Nova::Gsi_flags(Nova::Gsi_flags::HIGH);
else
return Nova::Gsi_flags(Nova::Gsi_flags::LOW);
};
_gsi_flags = gsi_flags(irq_args);
/* associate GSI or MSI to device belonging to device_phys */ /* associate GSI or MSI to device belonging to device_phys */
bool ok = false; bool ok = false;
if (device_phys) if (device_phys)
ok = msi(irq_sel(), device_phys, _msi_addr, _msi_data, _sigh_cap); ok = associate_msi(irq_sel(), device_phys, _msi_addr, _msi_data, _sigh_cap);
else else
ok = associate(irq_sel(), _msi_addr, _msi_data, _sigh_cap); ok = associate_gsi(irq_sel(), _sigh_cap, _gsi_flags);
if (!ok) if (!ok)
throw Service_denied(); throw Service_denied();
@@ -212,7 +228,9 @@ Irq_session_component::Irq_session_component(Range_allocator &irq_alloc,
: :
_irq_number(~0U), _irq_alloc(irq_alloc), _irq_object() _irq_number(~0U), _irq_alloc(irq_alloc), _irq_object()
{ {
long irq_number = Arg_string::find_arg(args, "irq_number").long_value(-1); Irq_args const irq_args(args);
long irq_number = irq_args.irq_number();
long device_phys = Arg_string::find_arg(args, "device_config_phys").long_value(0); long device_phys = Arg_string::find_arg(args, "device_config_phys").long_value(0);
if (device_phys) { if (device_phys) {
@@ -232,7 +250,7 @@ Irq_session_component::Irq_session_component(Range_allocator &irq_alloc,
_irq_number = (unsigned)irq_number; _irq_number = (unsigned)irq_number;
_irq_object.start(_irq_number, device_phys); _irq_object.start(_irq_number, device_phys, irq_args);
} }
@@ -241,7 +259,7 @@ Irq_session_component::~Irq_session_component()
if (_irq_number == ~0U) if (_irq_number == ~0U)
return; return;
Genode::addr_t free_irq = _irq_number; addr_t free_irq = _irq_number;
_irq_alloc.free((void *)free_irq); _irq_alloc.free((void *)free_irq);
} }
@@ -252,13 +270,13 @@ void Irq_session_component::ack_irq()
} }
void Irq_session_component::sigh(Genode::Signal_context_capability cap) void Irq_session_component::sigh(Signal_context_capability cap)
{ {
_irq_object.sigh(cap); _irq_object.sigh(cap);
} }
Genode::Irq_session::Info Irq_session_component::info() Irq_session::Info Irq_session_component::info()
{ {
if (!_irq_object.msi_address() || !_irq_object.msi_value()) if (!_irq_object.msi_address() || !_irq_object.msi_value())
return { .type = Info::Type::INVALID, .address = 0, .value = 0 }; return { .type = Info::Type::INVALID, .address = 0, .value = 0 };

View File

@@ -1,2 +0,0 @@
TARGET = ld-nova
LIBS = ld-nova

View File

@@ -19,8 +19,7 @@ src/kernel/okl4: src/kernel
content: content:
for spec in x86_32; do \ for spec in x86_32; do \
mv lib/mk/spec/$$spec/ld-okl4.mk lib/mk/spec/$$spec/ld.mk; \ mv lib/mk/spec/$$spec/ld-okl4.mk lib/mk/spec/$$spec/ld.mk; \
done; done;
sed -i "s/ld-okl4/ld/" src/lib/ld/okl4/target.mk
sed -i "s/pit_timer_drv/timer/" src/timer/pit/target.inc sed -i "s/pit_timer_drv/timer/" src/timer/pit/target.inc

View File

@@ -1 +1 @@
2022-05-24 3b2acba4ebd649394e26217802598cf650a4b226 2022-10-11 b81b8b94731cda35017a740b0110ff4e8e233e07

View File

@@ -1,2 +0,0 @@
TARGET = ld-okl4
LIBS = ld-okl4

View File

@@ -21,6 +21,5 @@ content:
for spec in x86_32; do \ for spec in x86_32; do \
mv lib/mk/spec/$$spec/ld-pistachio.mk lib/mk/spec/$$spec/ld.mk; \ mv lib/mk/spec/$$spec/ld-pistachio.mk lib/mk/spec/$$spec/ld.mk; \
done; done;
sed -i "s/ld-pistachio/ld/" src/lib/ld/pistachio/target.mk
sed -i "s/pit_timer_drv/timer/" src/timer/pit/target.inc sed -i "s/pit_timer_drv/timer/" src/timer/pit/target.inc

View File

@@ -1 +1 @@
2022-05-24 ca2c90ebcbaa61ade7373d6ea48a608912cd2629 2022-10-11 b522663f9c8c779f255e2a5eb37f98b4301c5446

View File

@@ -1,2 +0,0 @@
TARGET = ld-pistachio
LIBS = ld-pistachio

Some files were not shown because too many files have changed in this diff Show More