User-Guided Device Driver Synthesis
←
→
Page content transcription
If your browser does not render page correctly, please read the page content below
User-Guided Device Driver Synthesis∗ Leonid Ryzhyk1,2 Adam Walker2 John Keys3 Alexander Legg2 Arun Raghunath3 Michael Stumm1 Mona Vij3 1 University of Toronto 2 † NICTA and UNSW, Sydney, Australia 3 Intel Corporation Abstract lated the key principles behind the approach and demon- strated its feasibility by synthesizing drivers for several Automatic device driver synthesis is a radical approach real-world devices. The next logical step is to develop to creating drivers faster and with fewer defects by gener- driver synthesis into a practical methodology, capable of ating them automatically based on hardware device spec- replacing the conventional driver development process. ifications. We present the design and implementation of a To this end we have to address the key problems left open new driver synthesis toolkit, called Termite-2. Termite-2 by Termite-1. The most important one is the quality of is the first tool to combine the power of automation with synthesized drivers. While functionally correct, Termite- the flexibility of conventional development. It is also the 1 drivers were bloated and poorly structured. This made it first practical synthesis tool based on abstraction refine- impossible for a programmer to maintain and improve the ment. Finally, it is the first synthesis tool to support auto- generated code and prevented synthesized drivers from mated debugging of input specifications. We demonstrate being adopted by Linux and other major OSs. Further- the practicality of Termite-2 by synthesizing drivers for a more, it was impossible to enforce non-functional proper- number of I/O devices representative of a typical embed- ties such as CPU and power efficiency. ded platform. Another critical limitation of Termite-1 was the limited scalability of its synthesis algorithm, which made synthe- 1 Introduction sis of drivers for real-world devices intractable. Termite-1 Device driver synthesis has been proposed as a radical got around the problem by using carefully crafted simpli- alternative to traditional driver development that offers the fied device specifications, which is acceptable in a proof- promise of creating drivers faster and with far fewer de- of-concept prototype, but not in a practical tool. fects [24]. The idea is to automatically generate the driver In the present project we set out to address these limi- code responsible for controlling device operations from a tations. After several years of research we achieved sig- behavioral model of the device and a specification of the nificant improvement to all components of the synthesis driver-OS interface. technology: the specification language, the synthesis al- The primary motivation for device driver synthesis is gorithm and the code generator. the fact that device drivers are hard and tedious to write, Despite these improvements, we had come to the con- and they are notorious for being unreliable [8, 13]. Drivers clusion that the approach taken was initially critically generally take a long time to bring to production—given flawed. The fundamental problem, in our view, was that the speed at which new devices can be brought to mar- the synthesis was viewed as a “push-button” technology ket today, it is not uncommon for a device release to be that generated a specification-compliant implementation delayed by driver rather than silicon issues [33]. without any user involvement. As a result, the user had to Automatic driver synthesis was proposed in our ear- rely on the synthesis tool to produce a good implementa- lier work on the Termite-1 project [24], where we formu- tion. Unfortunately, even the most intelligent algorithm ∗ This cannot fully capture the user-perceived notion of high- research is supported by a grant from Intel Corporation. † NICTA is funded by the Australian Government through the Depart- quality code. While in theory one might be able to en- ment of Communications and the Australian Research Council through force some of the desired properties by adding appropri- the ICT Centre of Excellence Program. ate constraints to the input specification, in our experience 1
user-guided creating such specifications is extremely hard and seldom code yields satisfactory results. input specifications: generator synthesised driver - device model A radically different approach was needed—one that - OS model - driver template synthesis the most general strategy combines the power of automation with the flexibility of engine explanation of synthesis failure conventional development, and that involves the devel- oper from the start, guiding the generation of the driver. visual debugger corrected input specifications In many ways, synthesis and conventional development are conflicting. Hence, a key challenge was to conceive of Figure 1: Termite synthesis workflow. a way that allowed the two to be combined so that the de- veloper could do their job more efficiently and with fewer ing Termite-1, considered input specifications to be “cor- errors without having the synthesis tool get in the way. rect by definition”. In contrast, we recognise that input The primary contribution of this paper is a novel user- specifications produced by human developers are likely to guided approach to driver synthesis implemented in our contain defects, which can prevent the synthesis algorithm new tool called Termite-2 (further referred to as Termite). from finding a correct driver implementation. There- In Termite, the user has full control over the synthesis pro- fore Termite incorporates powerful debugging tools that cess, while the tool acts as an assistant that suggests, but help the developer identify and fix specification defects does not enforce, implementation options and ensures cor- through well-defined steps, similar to how conventional rectness of the resulting code. At any point during synthe- debuggers help troubleshoot implementation errors. sis the user can modify or extend previously synthesized Another important contribution of this project is a new code. The tool automatically analyses user-provided code scalable synthesis algorithm, which mitigates the compu- and, on user’s request, suggests possible ways to extend it tational bottleneck in driver synthesis. Following the ap- to a complete implementation. If such an extension is not proach proposed in Termite-1, we treat the driver synthe- possible due to an error in the user code, the tool generates sis problem as a two-player game between the driver and an explanation of the failure that helps the user to identify its environment, comprised of the device and the OS. In and correct the error. this work, we develop this approach into the first precise In an extreme scenario, Termite can be used to synthe- mathematical formulation of the driver synthesis problem size the complete implementation fully automatically. At based on game theory. This enables us to apply theoret- the other extreme, the user can build the complete imple- ical results and algorithmic techniques from game theory mentation by hand, in which case Termite acts as a static to driver synthesis. verifier for the driver. In practice, we found the interme- Our game-based synthesis algorithm relies on abstrac- diate approach, where most of the code is auto-generated, tion and symbolic reasoning to achieve orders of magni- but manual involvement is used when needed to improve tude speed up compared to the current state-of-the-art syn- the implementation, to be the most practical. thesis techniques. The main idea of the algorithm is de- From the developer’s perspective, user-guided syn- scribed in Section 4, but we refer the reader to a detailed thesis appears as an enhancement of the conventional description in an accompanying publication [30]. development process with very powerful autocomplete We evaluate Termite by synthesizing drivers for sev- functionality, rather than a completely new development eral I/O devices. Our experience demonstrates that our methodology. This vision is implemented in all aspects of methodology meets our design goals, and indeed makes the design of Termite. In particular, input specifications automatic driver synthesis practical. for driver synthesis are written as imperative programs Overview of Termite Figure 1 gives an overview of the that model the behavior of the device and the OS. The driver synthesis process, described in detail in the rest of driver itself is modelled as a source code template where the paper. Termite takes three specifications as its inputs: parts to be synthesized are omitted. This approach enables a device model that simulates software-visible device be- the use of familiar programming techniques in building havior, an OS model that specifies the software interface input specifications. In contrast, previous synthesis tools, between the driver and the OS, and a driver template that including Termite-1, require specifications to be written contains driver entry point declarations and, optionally, in formal languages based on state machines and temporal their partial implementation to be completed by Termite. logic, which proved difficult and error-prone to use even Given these specifications, driver synthesis proceeds in for formal methods experts, not to mention software de- two steps. The first step is carried out fully automati- velopment practitioners. cally by the Termite game-based synthesis engine, which Most previous research on automatic synthesis, includ- computes the most general strategy for the driver—a data 2
OS model and the OS, but are used by the OS model to specify cor- IO requests IO completions /*send_ack()*/ rectness constraints for the driver (see Section 2.3). /*send()*/ Finally, the driver template contains a partial driver im- virtual callbacks driver template /*evt_send(), evt_send_fail()*/ plementation to be completed by Termite. A minimal tem- device commands /*write_dat(), write_cmd(), read_cmd()*/ device notifications /*irq()*/ plate consists of a list of driver entrypoints without imple- mentation. At the other extreme, it can provide a complete device model implementation, in which case Termite acts as a static ver- ifier for the driver. Figure 2: Input specifications for driver synthesis. Labels in All specifications are written using the Termite Speci- italics show interfaces from the running example (Figure 3). fication Language (TSL). In line with our goal of making structure that compactly represents all possible correct synthesis as close to the conventional driver development driver implementations. This step encapsulates the com- workflow as possible, TSL is designed as a dialect of C putationally expensive part of synthesis. At the second with additional constructs for use in synthesis. We intro- step, the most general strategy is used by the Termite code duce relevant features of TSL throughout this section. generator to construct one specific driver implementation We minimize the amount of work needed to develop in C with the help of interactive input from the user. specifications for every synthesized driver by maximiz- The synthesis engine may establish that, due to a de- ing the reuse of specifications. In particular, Termite al- fect in one of the input specifications, there does not exist lows the use of existing device specifications developed a specification-compliant driver implementation. In this by hardware designers in driver synthesis. Furthermore, case, it produces an explanation of the failure, which can the OS specification for the driver can be derived from a be analysed with the help of the Termite debugger tool in generic specification for a class of similar devices (e.g., order identify and correct the defect. network or storage). Thus we expect that additional per- driver effort will consist of: (1) inserting device-class call- Limitations of Termite The device driver synthesis backs in appropriate locations of the device model and (2) technology is still in its early days and, as such, has sev- extending the OS specification to support device-specific eral important limitations. Most notably, Termite does features missing in the generic OS specification. not currently support synthesis or verification of code for managing direct memory access (DMA) queues. This 2.1 Device model code must be written manually and is treated by Termite The device model simulates the device operation at a level as an external API invoked by the driver. As another ex- of detail sufficient to synthesize a correct driver for it. To ample, in certain situations, explained in Section 3, Ter- this end, it must accurately model external device behav- mite is unable to produce correct code without user as- ior visible to software. At the same time, it is not required sistance; however it is able to verify the correctness of to precisely capture internal device operation and timing, user-provided code. We discuss limitations of Termite in as these aspects are opaque to the driver. more detail in Section 6. Such device models are routinely developed by hard- ware designers for the purposes of design exploration, 2 Developing specifications simulation, and testing. They are widely used by hard- Input to Termite consists of the three specifications, ware manufacturers in-house [14] and are available com- which model the complete system consisting of the driver, mercially from major silicon IP vendors [28]. These mod- the device, and the OS, shown in Figure 2. The OS and els are known as transaction-level models (TLMs) (in device models simulate the execution environment of the contrast to the detailed register-transfer-level models used driver and specify constraints on correct driver behavior. in gate-level synthesis) [4]. A TLM focuses on software- The device model simulates software-visible device be- visible events, or transactions, such as a write to a device havior. The OS model serves as a workload generator that register or a network packet transmission. issues I/O requests to the driver and accepts request com- Existing TLMs created by hardware designers can be pletions in a way consistent with real OS behavior. used with minor modifications (explained in Section 2.3) The virtual interface between the device and the OS, for driver synthesis. Model reuse dramatically reduces the shown with the dashed arrow in Figure 2, is used by the effort involved in synthesizing a driver and is therefore device model to notify the OS model about important crucial to practical success of driver synthesis. By reusing hardware events, such as completion of I/O transactions an existing model, we also reuse the effort invested by and error conditions. Methods of the virtual interface do hardware designers into testing and debugging the model not represent real runtime interactions between the device throughout the hardware design cycle, thus making driver 3
1 template dev /* Device model */ synthesis less susceptible to specification bugs. Finally, 2 uint8 reg_dat, reg_cmd, reg_status = 0; 3 /* device commands */ since TLMs are created early in the hardware design cy- 4 controllable void write_dat(uint8 v) cle, TLM-based driver synthesis can be carried out early 5 { reg_dat = v; }; as well, thus removing driver development from the criti- 6 controllable void write_cmd(uint8 v) cal path to product delivery. 7 { reg_cmd = v; }; 8 controllable uint8 read_cmd() TLMs are written in high-level hardware description 9 { return reg_cmd; }; languages like SystemC and DML. In order to use these 10 controllable uint8 read_status() models in driver synthesis, we need to convert them to 11 { return reg_status; }; 12 /* internal behavior */ TSL. This translation can be performed automatically, and 13 process ptx { we are currently working on a DML-to-TSL compiler. 14 forever { Since this work is not yet complete, device models used in 15 wait (reg_cmd == 1); the experimental section of this paper are either manually 16 choice { 17 { os.evt_send(reg_dat); translated from existing TLMs or written from scratch us- 18 reg_status=0; }; ing TLM modeling style guidelines [31]. 19 { os.evt_send_fail(reg_dat); The top part of Figure 3 shows a fragment of a model 20 reg_status=1; }; 21 }; of a trivial serial controller device used as a running ex- 22 reg_cmd = 0; ample. The fragment specifies the send logic of the con- 23 /*drv.irq(); (see Section 4*/ troller, which allows software to send data characters over 24 }; the serial line. The model is implemented as a TSL tem- 25 }; 26 endtemplate plate. The template encapsulates data and code that ma- 27 nipulates the data, similar to a class in OOP. 28 template os /* OS model */ The software interface of the device consists of 29 uint8 dat; 30 bool inprogress, acked, success; data, command, and status registers declared in line 2. 31 /* driver workload generator */ The registers can be accessed from software via the 32 process psend { write dat, write cmd, read cmd, and read status 33 forever { 34 dat = *; /*randomise dat*/ methods (lines 4–11). The controllable qualifier de- 35 inprogress = true; notes a method that is available to the driver and can be 36 acked = false; invoked from synthesized code. 37 drv.send(dat); The transmitter logic is modelled in lines 13–25. It is 38 wait(acked); 39 }; implemented as a TSL process. A TSL specification can 40 }; contain multiple processes. The choice of the process to 41 /* I/O completions */ run is made non-deterministically by the scheduler. The 42 controllable void send_ack(bool status) { 43 assert (!inprogress && !acked && process executes atomically until reaching a wait state- 44 status == success); ment or a controllable placeholder (see below). 45 acked = true; In line 15, the transmitter waits for a command, issued 46 }; 47 /* virtual callbacks */ by the driver by writing value 1 to the command register. 48 void evt_send(uint8 v) { Upon receiving the command, it sends the value in the 49 assert (inprogress && v==dat); data register over the serial line. The transmission may 50 inprogress = false; fail, e.g., due to a serial link problem. The device signals 51 success = true; 52 }; transmission status to software by setting the status regis- 53 void evt_send_fail(uint8 v) { ter to 0 or 1. Finally, it clears the command register, thus 54 assert (inprogress && v==dat); notifying the driver the request has completed. 55 inprogress = false; 56 success = false; Internally, the transmitter circuit consists of a shift reg- 57 }; ister and a baud rate generator used to output data on 58 goal idle_goal = acked; the serial line. These details are not visible to soft- 59 endtemplate ware and are abstracted away in the model. We use 60 61 template drv /* Driver template */ the non-deterministic choice construct to choose be- 62 void send(uint8 v){...;}; tween successful transmission and failure, without mod- 63 /*void irq(){...;}; (see Section 4)*/ elling the details of serial link operation. Successful and 64 endtemplate failed transmissions are modelled using evt send and Figure 3: Trivial serial controller driver specifications. evt send fail events, explained in Section 2.2. 4
2.2 OS model specification reuse, we would like to keep the OS specifi- cation device-independent. To reconcile these conflicting The OS model specifies the API mandated by the OS for requirements, we introduce a virtual interface between the all drivers of the given type. For example, any Ether- device and OS model. This interface consists of callbacks net driver must implement the interface for sending and used by the device model to notify the OS model about receiving Ethernet packets. A separate specification is important hardware events. The virtual interface does not needed for each supported OS, as different OSs define dif- represent real runtime interactions between the device and ferent interfaces for device drivers. the OS, but serves as part of the correctness specification. Additionally, each particular device can support non- standard features, e.g., device-specific configuration op- We define a virtual interface for each class of devices. tions or transfer modes. These features must be added as Such device-class interfaces are both device and OS- extensions to the generic OS specification in order to syn- independent. The device-class interface can be extended thesize support for them in the driver. TSL supports such with additional device-specific callbacks as required to extensions in a systematic way via the template inheri- specify a driver for a particular device. tance mechanism. We do not describe this in detail due to limited space. In our example, we define a device-class interface The OS model is written in the form of a test harness consisting of two virtual callbacks: evt send and that simulates all possible sequences of driver invocations ev send failed, invoked respectively when the device issued by the OS. The os template in Figure 3 shows the successfully transmits and fails to transmit a byte. These OS model for our running example. The main part of the callbacks are invoked in lines 17 and 19 of the device model is the psend process. At every iteration of the loop, model. The evt send handler is shown in line 48 of it non-deterministically chooses an 8-bit value (line 34) the OS model. The assertion in line 49 specifies that the and calls the send method of the driver, passing this value send event is only allowed to occur if there is an outstand- as an argument. It then waits for the driver to acknowl- ing send request in progress and the value being sent is edge the transmission of the byte (line 38) before issuing the same as the one requested by the OS. We reset the another request. The driver acknowledges the transmis- inprogress flag to false in line 50, thus marking the cur- sion via the send ack callback (line 42). The callback rent request as completed; line 51 sets the success flag sets the acked flag, which unblocks the psend process. to true, thus indicating that the transfer completed with- We keep the specification concise by modeling the state out an error. The evt send fail handler is identical, of the driver-OS interface, as opposed to the internal OS except that it sets the success flag to false. The flags state and behavior. For example, the acked variable are checked by the send ack method, which asserts that (line 30) serves to model the flow of data between the OS the driver is only allowed to acknowledge a completed and the driver and is not necessarily present in the OS im- request (!inprogress) that has not been acknowledged plementation. yet (!acked) and that the completion status reported by the driver must match the one recorded in the success 2.3 Connecting device and OS models flag. In addition to simulating I/O requests to the driver, the In this example we use C-style assertions to rule out OS model also specifies the semantics of each request in invalid system behaviors. Assertions alone do not fully terms of device-internal events that must occur in order to capture requirements for a correct driver behavior. For complete the requested I/O operation. In our running ex- example, a driver that remains idle does not violate any as- ample, after the OS invokes the send method of the driver sertions. Hence, we need to specify requirements for the and before the driver acknowledges completion of the re- driver to make forward progress. We introduce such re- quest, the device must attempt to send the requested data quirements into the model in the form of goal conditions, over the serial line. This requirement establishes a con- that must hold infinitely often in any run of the system. For nection between the device and OS models and must be example, a goal may require that the driver is infinitely of- specified explicitly in order to enable Termite to generate ten in an idle state with no outstanding requests from the a driver implementation that correctly handles the OS re- OS. The OS can force the driver out of the goal by issu- quest. Note that we only need to specify which hardware ing a new I/O request. To satisfy the goal condition, the events must occur, but not how the driver generates them. driver must return to the goal state by completing the re- In order to develop such specifications, we need a way quest. Line 58 in Figure 3 defines such a goal condition to refer to relevant state and behavior of the device from that holds whenever the acked flag is set, i.e., the driver the OS model. At the same time, in order to maximize has no unacknowledged send requests. 5
2.4 Driver template out branching. For example, when running the generator The bottom part of Figure 3 shows the driver template on the specification in Figure 3, it automatically generates for the running example consisting of a single send entry the following code for the send function (line 62): point invoked by the OS. The ellipsis in line 62 represent void send(uint8 v){ dev.write_dat(v); a location for inserting synthesized code and are part of dev.write_cmd(1); TSL syntax. We refer to such locations as controllable wait(dev.reg_cmd==0); placeholders. if (os.success) { os.send_ack(true); } else { 3 User-guided code generation os.send_ack(false); The set of input TSL specifications is fed into the Ter- };} mite synthesis engine, which then automatically computes This implementation correctly starts the data transfer by the most general strategy for the driver. Given a state of writing the value to be sent to the data register and setting the system, the most general strategy determines the set the command register to 1. It then waits for the transfer of all valid driver actions in this state. The most general to complete, which is signalled by the device by resetting strategy is used by the Termite code generator to produce the command register to 0. Finally, it acknowledges the a driver implementation in C in a user-guide fashion. completion of the transfer to the OS. The Termite code generator GUI is similar to a tradi- Note that the generated code refers to the dev.reg cmd tional integrated development environment with two ad- and os.success variables. These variables model in- ditional built-in tools: the generator and the verifier. The ternal device and OS state respectively and cannot be di- generator works as advanced auto-complete that helps the rectly accessed by the driver. This example illustrates an user to fill the controllable placeholders inside the driver important limitation of Termite—it assumes a white-box template with code. At any point, the user can invoke model of the system, where every state variable is visi- the generator to synthesize a single statement or a com- ble to the driver. Ideally, we would like to synthesize an plete block of code inside a controllable placeholder via a implementation that automatically infers the values of im- mouse click on the target code location. The user can ar- portant unobservable variables. In this case, the value of bitrarily modify and amend the generated code. However, the command register can be obtained by the driver by ex- the generator never modifies user code. Instead it tries to ecuting the read cmd action. Furthermore, the value of extend it to a complete implementation, which is always the os.success variable is correlated with the comple- possible provided that the existing code is consistent with tion status of the last transfer, which can be obtained by the most general strategy. The generator currently only al- reading the device status register. lows synthesizing statements after the last control location While Termite currently cannot produce such an imple- within a branch. However this restriction is not a concep- mentation automatically, it implements a pragmatic trade- tual one and will be lifted by ongoing development. off that helps the user build and validate a correct im- The verifier automatically and on the fly checks that plementation with modest manual effort. The code gen- the driver implementation, comprised of a mix of gen- erator warns the user that the auto-generated code ac- erated and manually written code, is consistent with the cesses private variables of the device and OS templates. most general strategy, thus maintaining strong correctness This prompts the user to provide a functionally equiva- guarantees that one would expect in automatically synthe- lent valid implementation, replacing the wait statement sized code. The verifier symbolically simulates execution with a polling loop and using the read status method of the system, following the partial driver implementation to check transfer status: created so far, and signals the user whenever it encounters void send(uint8 v){ a transition that violates the most general strategy. dev.write_dat(v); In the first approximation, the generator algorithm is dev.write_cmd(1); quite simple: given a source code location, it determines while(dev.read cmd()==1); if (dev.read status()) { the set of possible system states in this location, picks an os.send_ack(true); action for each state from the most general strategy and } else { translates this action into a code statement. In practice the os.send_ack(false); algorithm uses a number of heuristics to produce compact };} and human-readable code. In particular, whenever there The verifier automatically checks the resulting implemen- exists a common action in all possible states in the given tation and confirms that it satisfies the input specification. location, the algorithm produces straight-line code with- Note that in this example we have synthesized code 6
that correctly handles device errors. This was possible, dev.write_dat(v); as our input device specification correctly captures device dev.write_cmd(1);} failure modes (namely, transmission failure) and our OS Finally, we run Termite on the resulting specifications and specification describes how the driver must report errors use the generator to automatically produce the following to the OS (via the status argument of the completion implementation of the new irq method: callback). void irq(){ In principle, it is also possible to synthesize a driver im- if (os.success) { plementation that handles device and OS failures not cap- os.send_ack(true); tured in the specifications: since the synthesis tool knows } else { all possible valid environment behaviors, it can easily de- os.send_ack(false); };} tect invalid behaviors and handle them gracefully. Au- tomatic synthesis of such hardened device drivers is a As before, we manually replace the if-condition in the first promising direction of future research. line with The final step of the code generation process translates if (dev.read_status()) the synthesized driver implementation to C. This is a triv- This example illustrates how Termite supports incre- ial line-by-line translation. We expect this translation to mental changes to the driver by reusing previously syn- become unnecessary in the future as our ongoing work on thesized code, while maintaining strong correctness guar- the TSL syntax aims to make the synthesized subset of antees. TSL a strict subset of C. Instrumenting synthesized code Termite does not auto- Maintaining synthesized code Device driver develop- matically instrument synthesized code for debugging, log- ment is not a one-off task: following the initial implemen- ging, accounting, etc. However, the user can add such in- tation, drivers are routinely modified to implement addi- strumentation manually. Termite interprets such code as tional functionality, adapt to the changing OS interface or no-ops and, as with any manual code, never makes any support new device features. modifications to it. The user-guided code generation method naturally sup- ports such incremental maintenance. A typical main- tenance task proceeds in three steps. First, the devel- 4 Synthesis oper amends device and OS models to reflect the new In this section we give a high-level overview of the Ter- or changed functionality. Second, they add new methods mite synthesis algorithm. We refer the reader to the ac- to the previously synthesized driver, if necessary, and re- companying publication [30] for a detailed description. place existing driver code that is expected to change with a controllable placeholder. Finally, the user runs Termite to 4.1 Driver synthesis as a game synthesize code for all controllable placeholders. Termite We formalize the driver synthesis problem as a two-player treats all existing driver code as part of the uncontrollable game [29] between the driver and its environment. The environment. Hence, if some of the old code is incorrect game is played over a finite automaton that represents all in the context of the new specifications, this will lead to a possible states and behaviors of the system. Transitions synthesis failure, and counterexample-based debugging is of the automaton are classified into controllable transi- used to identify the faulty code, as described in Section 5. tions triggered by the driver and uncontrollable transitions As an example, we synthesize a new version of the triggered by the device or OS. A winning strategy for the driver for our running example assuming a more advanced driver in the game corresponds to a correct driver imple- version of the serial controller device that uses interrupts mentation. If, on the other hand, a winning strategy does to notify the driver on completion of a data transfer. The not exist, this means that there exists no specification- new device model is obtained by uncommenting line 23 conforming driver implementation. of the device model in Figure 3, which invokes the inter- Two-player games naturally capture the essence of the rupt handler method of the driver after each transfer. The driver synthesis problem: the driver must enforce a cer- driver template is extended with the irq method (line 63). tain subset of system behaviors while having only partial We use the previously synthesized implementation of the control over the system. send method, but manually remove the last two lines, Figure 4 illustrates the concept using a trivial game au- which implement polling, as we want the new implemen- tomaton that models the core of our running example. tation to use interrupts instead: Controllable and uncontrollable transitions of the automa- void send(uint8 v){ ton are shown with solid and dashed arrows respectively. 7
send_ack π(s) are winning moves in s. The game G is winning for s3 write_data write_cmd the driver if all states in I are winning. The most general winning strategy maps every winning state s to a set of all G s2 s5 s6 send evt_send winning moves in s, and all other states to an empty set. write_cmd write_data In Termite we use game objectives of a particular form, s4 E evt_send called generalised reactivity-1 (GR-1) objectives [22]. Such an objective consists of a finite set {B1 , ...Bn }, Figure 4: A simple two-player game. Bi ⊆ S of goal sets and a finite set {F1 , ...Fk }, Fi ⊆ S of fair sets. A winning strategy for the driver must make sure The goal of the driver in the game is to infinitely often visit that the game infinitely often visits each of the goal sets, the initial state, labelled G, which represents the situation provided that the environment guarantees that the game when the driver does not have any outstanding requests. does not get stuck in a fair set forever. After getting a send request from the OS, the driver must Intuitively, a goal set represents a constraint on the write data and command registers to start the data transfer. driver behavior, requiring the driver to force the game into Writing the command register first may trigger a hardware the goal infinitely often, while a fair set represents a con- send event before the driver has a chance to write the data straint on the environment, preventing it from staying in register. As a result, wrong data value gets sent, taking certain states forever. The game in Figure 4 has a single the game into an error state E. Hence, state s4 is losing goal set B1 = {g} and a single fair set F1 = {s4 , s5 }, i.e., for the driver. To avoid this state, the correct strategy for the driver must acknowledge each send request from the the driver is to play write data in state s2 , followed by OS, provided that the environment eventually performs write cmd. In s5 the driver must remain idle until the the evt send action after it has been enabled. environment executes the evt send transition. Games and strategies Formally, a two-player game 4.2 TSL compiler G = hS, I, Lc , Lu , δc , δu , Φi consists of a set of states In order to compute the most general driver strategy as a S, a subset of initial states I ⊆ S, sets of controllable and solution of a two-player game, we must first convert input uncontrollable actions Lc and Lu , controllable transition TSL specifications into a game automaton. This conver- relation δc ⊆ S×Lc ×S, uncontrollable transition relation sion is performed by the TSL compiler. δu ⊆ S × Lu × S, and a game objective Φ ⊆ S ω (where Real driver specifications have large state spaces, which S ω represents the set of infinite sequences of states in S). cannot be feasibly represented by explicitly enumerating The game proceeds in rounds, starting from an ini- states, as in Figure 2. Therefore, in Termite we repre- tial state. In each round, in state s, both players select sent games symbolically. The state space of the game is actions lc and lu available to them in s, and the game defined in terms of a finite set of state variables X, with transitions non-deterministically to one of the states in each state s ∈ S representing a valuation of variables in δc (s, lc ) ∪ δu (s, lu ). Intuitively, the system scheduler X. The TSL compiler introduces a state variable for each chooses the player to make a move at each round. The TSL variable declared in one of the input templates. In scheduler can be thought of as part of the uncontrollable addition, auxiliary state variables are introduced to model environment. Note that this is different from turn-based the current control location of each TSL process. games like chess, where players strictly alternate in mak- We model controllable and uncontrollable actions as ing moves. In the example in Figure 4, the driver can valuations of action variables Yc and Yu . Transition rela- avoid the error state by choosing the write dat action in tions δc and δu are represented symbolically as formulas state s4 ; however the environment can override this choice over state variables X, action variables Yc and Yu , and by playing evt send. next-state variables X 0 . The infinite sequence of states (s0 , s1 , ...) ∈ S ω vis- The TSL compiler splits the input specification ited by the game is called a run. A strategy for the driver into controllable and uncontrollable parts and trans- player is a function π : S → 2Lc that maps each state of lates them into controllable and uncontrollable transi- the game into a set of actions to play in this state. The tion relations respectively. The controllable part is strategy determines a set Outcomes(I, π) ⊆ S ω of all comprised of controllable methods that can be in- possible runs generated by the driver choosing one of the voked by the driver. The controllable transition re- actions in π(s) in every state s in the run. lation δc is computed by rewriting controllable meth- Given a state s and a strategy π such that ods in the variable update form. Consider, for ex- Outcomes({s}, π) ⊆ Φ, we say that s is a winning state ample, variable reg dat declared in line 2 in Fig- for the driver, π is a winning strategy in s, and actions in ure 3. This variable is only modified by the write dat 8
method in line 4. The corresponding fragment of the Algorithm 1 Computing the set of winning states controllable transition relation in the variable update function R EACH(B) form is reg dat0 := (tag = write dat) ? v : reg dat, Y ←∅ loop where reg dat0 is the next-state variable representing Y 0 ← CP re(Y ∪ B) the value of reg dat after the transition, and tag and if Y 0 = Y return Y v are controllable action variables, where tag models Y ←Y0 the method being invoked, and v is the argument of the function W INNING S ET({B1 , ..., Bn }) method. Z←S The uncontrollable part of the specification is com- loop Z 0 ← i=1..n R EACH(Z ∩ Bi ) T prised of TSL processes, which model device and OS 0 if Z = Z return Z behavior. We syntactically decompose each process Z ← Z0 into atomic transitions. Recall that a process executes atomically until reaching a wait statement or a con- The algorithm is based on exhaustive exploration of the trollable placeholder. Consider the ptx process in state space of the game. Given a goal set B, we first de- line 13 in Figure 3. The process is initially paused in termine the set of states from which the driver can force the wait statement. It is scheduled to run when the wait the game into B in one step, called the controllable pre- condition holds. It executes the statements in lines 16–22 decessor of B. The controllable predecessor consists of atomically and stops again in line 15. As part of this all states s that satisfy both of the following conditions: atomic transition, the process sets the reg cmd variable to 0 (line 22). This is the only uncontrollable transition 1. All uncontrollable transitions available in s lead to that modifies this variable, hence the uncontrollable some state in B. Hence, if the scheduler chooses to update function for this variable is defined as follows: execute an uncontrollable transition, it is guaranteed reg cmd0 := (reg cmd = 1 ∧ pid = ptx) ? 0 : reg cmd, to take the game to B. where pid is an uncontrollable action variable that mod- 2. There exists at least one winning controllable tran- els the scheduler’s choice of a process to run, and the sition from s to B or s belongs to a fair region. In reg cmd = 1 conjunct corresponds to the wait condition the former case, the driver must perform the winning in line 15. transition; in the latter case it must remain idle wait- Finally, we need to generate the game objective Φ. In a ing for an uncontrollable transition, which is guaran- symbolic representation of the game, goal and fair sets are teed to occur due to fairness. specified as conditions over state variables that hold for each state in the set. The TSL compiler outputs a goal set Having computed the controllable predecessor of B, we Bi for each goal declared in the input specification and a apply the controllable predecessor operator again to the fair set Fi for each wait statement. The latter guarantees resulting set, thus obtaining the set of states from which that every runnable process gets scheduled eventually. the driver can force the game into the goal within two In addition to goal conditions, a TSL specification also steps. We repeat until no new states can be discovered, contains assertions, which must never be violated. We at which point we have found all states from which the model assertions using an auxiliary boolean state variable driver can force the game into the goal in a finite number ε, which is set to true whenever an assertion is violated of rounds. This computation is performed by the R EACH and remains true forever after. We add an extra constraint function shown in Algorithm 1. ε = f alse to each accepting set Bi . An assertion viola- Recall that a GR-1 game can have multiple goal re- tion permanently takes the game out of Bi , and therefore gions, and in order to win the game the driver must visit can not occur in any winning run of the game. each goal region Bi infinitely often. T Using the R EACH function, we compute the set Z = i R EACH(Bi ), from 4.3 Solving the game which any of the goalsT can be reached at least once. Next, The Termite game solver takes a game automaton pro- we compute Z 0 = i R EACH(Z ∩ Bi ). It is easy to see duced by the TSL compiler, determines whether all initial that Z 0 contains all states from which any of the goals can states of the system are winning and, if so, computes the be reached twice. Furthermore, by construction, Z 0 ⊆ Z. most general winning strategy for the game. A successful By continuing the last computation until a fixed point is approach to solving two-player games with GR-1 objec- reached, we obtain all winning states of the game, as tives was proposed by Piterman et al. [22]. We give an shown in function W INNING S ET (Algorithm 1). overview of their algorithm and briefly explain how we The algorithm presented above is polynomial in the size extend it to address the scalability bottleneck. of the game automaton. We have developed a highly opti- 9
mized implementation of the algorithm, which uses sym- In order to detect and fix the defect in an input specifi- bolic data structures [3] to compactly represent large sets cation, the driver developer relies on their understanding of states and transitions. Nevertheless, when applying it to of the OS and device logic. The role of the counterexam- games arising in driver synthesis, we hit a computational ple strategy is to guide the developer towards the defect. bottleneck due to a state explosion. To automate this process, we developed a powerful visual We overcome this bottleneck by using abstraction to debugging tool that allows the user to interactively sim- reduce the dimensionality of the problem. The partic- ulate intended driver behavior and observe environment ular form of abstraction used by Termite is predicate responses to it. The user plays the game on behalf of the abstraction [12], where concrete state variables of the driver, while the tool responds on behalf of the environ- game are replaced with boolean predicates over the orig- ment, according to the counterexample strategy. inal variables. Abstraction is adaptively refined by in- In a typical debugging session, the debugger, following troducing new predicates that capture important rela- the counterexample strategy, generates a sequence of re- tions among concrete variables. The predicate-based quests that are guaranteed to win against the driver. The abstraction-refinement algorithm for games is one of the user plays against these requests by specifying device key technical contributions of Termite. It is described in commands that, they believe, represent a correct way to detail in an accompanying paper [30]. handle the request. Since this sequence of requests can- not be handled correctly given the current input specifi- 4.4 Verification as a special case of synthesis cation, at some point in the game the user runs into an Consider the situation where not only the OS and the de- unexpected behavior of one of the players, e.g., one of vice, but also the driver behavior is fully specified, so that user-provided commands does not change the state of the the synthesizer does not have any freedom to pick driver device as expected or the environment performs an uncon- actions. If the resulting game is winning for the driver, trollable transition that violates an assertion. Based on this i.e., every possible run of the game satisfies the objective, information, the user can revise the faulty specification. then the provided driver implementation is correct. Thus, At every step of the interactive debugging session, the verification can be seen as a special case of the synthesis debugger either chooses a spoiling uncontrollable action problem where all transitions in the system are uncontrol- based on the counterexample strategy or, if the system lable. Hence, our game solving algorithm doubles as a is inside a controllable placeholder, allows the user to driver verification algorithm. Termite also supports hy- choose a controllable action to execute on behalf of the brid scenarios: given a partially implemented driver with driver. In the former case the spoiling uncontrollable ac- placeholders for synthesized code, it determines whether tion corresponds to a transition in one of the TSL pro- the given partial implementation can be extended to a cesses. The user can explore this transition by stepping complete one and, if so, fills out the placeholders in the through it, exactly as they would in a conventional debug- user-guided fashion. ger. In the latter case, the user provides the action that they would like to perform by typing and executing corre- 5 Debugging with counterexamples sponding code statements. The tool supports a number of features aimed to make An important practical issue in game-based synthesis is the debugging process as simple as possible for the user. the complexity of diagnosing synthesis failures due to de- We mention two of them here. First, the debugger interac- fects in the input specifications. In the event that Termite tively prompts actions available to the driver at each step. fails to solve the game, the user needs to trace the failure Second, the debugger keeps the entire history of the game back to the specification defect. However, the failure does and allows the user to go back to one of previously ex- not carry any information about the defect, which makes plored states and try a different behavior from there. the problem harder to resolve. In Termite we propose a new approach to troubleshoot- ing synthesis failures based on the use of counterexample 6 Limitations of Termite strategies. A counterexample strategy is a strategy on be- In Section 3, we described one limitation of Termite, half of the environment that prevents the driver from win- namely the lack of support for grey-box synthesis. In this ning the game. It is obtained by solving the dual game, section we discuss other limitations, which, we hope, will where, in order to win, the environment must permanently help define the agenda for continuing research in driver force the game out of one of the goal regions. A winning synthesis. strategy in the dual game is guaranteed to exist whenever Most importantly, Termite does not currently support solving of the primary game fails. automatic synthesis of direct memory access (DMA) 10
management code. Many modern devices transfer data havior without having to explicitly reason about time. To directly to and from main memory, where it is buffered this end, Termite conservatively approximates timed oper- in data structures such as circular buffers and linked lists. ations by fairness constraints: it ignores the exact duration These data structures can have very large or infinite state of each device operation, but keeps the knowledge that spaces and cannot be easily modeled within the finite state the operation will complete eventually, and synthesizes a machine-based framework of Termite. Efficient synthesis driver that waits for the completion. Termite is also able for DMA requires enhancing the synthesis algorithm to to handle time-out conditions, modeled as external events. use more compact representation of DMA data structures, However, at this time it is not capable of generating device which is the focus of our ongoing research. At this time, drivers for hard real-time systems, where the driver must code for manipulating DMA data structures must be writ- guarantee completion of I/O operations by a certain dead- ten manually. This code is not interpreted or verified by line. Termite. For example, we use this approach to synthesize a DMA-capable IDE disk driver (Section 7). 7 Implementation and evaluation Device drivers in modern OSs contain a significant The version of Termite presented here consists of amount of boilerplate code that is not directly related to 30,000 lines of Haskell code. The estimated overall the task of controlling the device. This includes binding project effort is 10 person years. Termite is available in the driver to I/O resources (memory mapped regions, in- source and binary form from the project webpage1 . terrupts, timers), registering the driver with various OS We evaluate Termite by synthesizing drivers for eight subsystems, allocating DMA memory regions, creating I/O devices. Specifically, we synthesized drivers for a sysfs entries, etc. While much of this functionality could UVC-compliant USB webcam, the 16550 UART serial be synthesized within the game-based framework, we do controller, the DS12887 real-time clock, and the IDE disk not believe that this is the correct approach. Previous re- controller for Linux, as well as seL4 [16] drivers for I2C, search has demonstrated that this boilerplate code can be SPI, and UART controllers on the Samsung exynos 5 generated in a principled way from declarative specifica- chipset2 and SPI controller on the STM32F10 chipset. tions of the driver’s requirements and capabilities [26]. With the exception of the IDE disk, these devices are This technique has lower computational complexity than representative of peripherals found in a typical embed- game solving and better captures the essence of the task. ded platform, such as a smartphone. Our synthesized A practical driver synthesis tool can combine game-based drivers implement data transfer, configuration and error synthesis of the core driver logic responsible for control- handling. The main barrier to synthesizing drivers for ling the device with declarative synthesis of boilerplate more advanced devices, e.g., high-performance network code. As a result, the current version of Termite assumes controllers, is the current lack of support for synthesis of this boilerplate code is written manually as a wrapper DMA code in the current version of Termite. around the synthesized driver. Modelling complexity Models of UART and DS12887 Drivers execute in a concurrent OS environment and devices were developed based on existing publicly avail- must handle invocations from multiple threads, as well as able device models [32, 20]. Models of other devices were asynchronous hardware interrupts. We separate synthesis derived from their vendor-provided documentation, fol- for concurrency into a separate step. Drivers synthesized lowing standard TLM modeling guidelines [31]. OS mod- by Termite are correct assuming a sequential environment, els for the relevant device classes were created based on where driver entry points are invoked atomically. The re- Linux kernel documentation and source code. sulting sequential driver is then processed by a separate Table 1 summarises the size, in lines of code, of device tool that performs a sequence of transformations of the and OS models in our case studies. Developing a com- driver source code, which preserve the driver’s sequential plete set of specifications for each driver took approxi- behavior, while making the driver thread-safe. Such trans- mately one week, of which only one to three days were formations include adding locks around critical code sec- spent building the models and the rest of the time was tions, inserting memory barriers, and reordering instruc- spent studying device and OS documentation. This effi- tions to avoid race conditions. Concurrency synthesis is ciency can be attributed to the choice of the right level of still work in progress and is beyond the scope of this pa- per. Our preliminary results are published in [5, 6]. 1 http://termite2.org 2 At the time of writing, the exynos drivers have not yet been tested Termite does not explicitly support specification and due to hardware availability issues; however we confirmed via manual synthesis of timed behaviors. Instead, it uses a pragmatic inspection that they implement the same device control sequences as approach that allows it to synthesize time-sensitive be- existing manually developed drivers. 11
input spec driver refine- predi- synt. verif. vars(bits) OS device synthesized native ments cates time (s) time (s) webcam 102 385 113 307 webcam 128 (125565) 47 192 215 794 16450 UART 122 167 74 261 16450 UART 81 (407) 65 128 210 464 exynos UART 128 252 37 166 exynos UART 80 (1185) 54 111 645 82 STM SPI 73 244 24 64 STM SPI 68 (389) 29 63 67 31 exynos SPI 88 239 40 183 exynos SPI 83 (933) 31 72 25 44 exynos I2C 146 180 79 211 exynos I2C 65 (303) 21 56 45 96 RT clock 118 252 84 183 RT clock 92 (810) 25 74 56 127 a IDE 188 480 94 474 IDE 114 (1333) 42 105 285 778 a Excluding 36 lines of manually written code that manipulates the Table 2: Performance of the Termite game solver. DMA descriptor table. the input specifications in terms of the number of states Table 1: Size (in lines of code) of input specifications and of variables and the total number of bits in these variables. synthesized and equivalent manually written drivers. The third column shows the number of iterations of the abstraction refinement loop required to solve the game. abstraction and modeling language. In particular, the use The next column shows the size of the abstract game at of transaction-level device modeling abstracts away com- the final iteration, in terms of the number of predicates plicated internal device machinery by focusing on high- in the abstract state space of the game. These results level events relevant to driver synthesis, while the TSL demonstrate the dramatic reduction of the problem dimen- language allows modeling the driver environment using sion achieved by our abstraction refinement method. The standard programming techniques, as illustrated by our second-last column shows that the Termite game solver running example. was able to find the most general winning strategy within Interestingly, we found the most error-prone step in de- a few minutes in all case studies. veloping specifications for driver synthesis to be defin- We compared the performance of the Termite game ing correct relative ordering of OS-level and device-level solver against a state-of-the-art abstraction refinement al- events with the help of the virtual interface (Section 2.3). gorithm for games [10] as well as against the standard Naı̈ve specifications tend to be either too restrictive, lead- symbolic algorithm for solving games without abstrac- ing to synthesis failures, or too liberal, leading to incorrect tion [22]. In all case studies, the Termite solver was the synthesized drivers. As we gained more experience syn- only one to find a winning strategy within a two-hour thesizing different types of drivers, we identified common limit. We refer the reader to [30] for a more detailed per- modeling patterns that help avoid errors in virtual inter- formance analysis of the Termite synthesis algorithm. face specifications. The final column of Table 2 shows the time that it took As a common example, most virtual interfaces contain Termite to verify a complete driver. Recall that the Ter- callbacks that signal a change to one of device configura- mite synthesis algorithm doubles as a verification algo- tion parameters, e.g., transfer speed, parity, etc. A naı̈ve rithm and can be used to verify drivers written in TSL. OS model may only allow such a callback to be triggered We used complete synthesized drivers, containing a com- when the OS has requested a change to the corresponding bination of manual and automatically generated code, as device setting. However, many devices only allow setting inputs to Termite. We have been able to successfully ver- multiple configuration parameters simultaneously, so that ify all of our drivers. We also experimented with intro- setting any individual parameter triggers multiple call- ducing faults to synthesized drivers. Termite was able backs, thus making the specification non-synthesizable. to detect these faults and produce correct counterexample The problem can be rectified by changing the device spec- strategies. In most cases verification took longer than syn- ification to only trigger callbacks if the new value of the thesis. The reason for this is that Termite has not yet been parameter is different from the old one; however this optimized for verification workloads. This is one area for bloats the device model due to the extra checks. A better future improvement. solution, used in all our models, is to design the OS speci- User-guided code generation and debugging We eval- fication to allow configuration callbacks to be triggered at uate the key contribution of this paper, namely the user- any time, provided that the new value of the parameter is guided debugging and code generation technique. Each equal to the last value requested by the OS. line of code in a Termite-generated driver originates from Synthesis time Table 2 summarises the performance of one of three sources: it can be (1) synthesized automati- the Termite game solver in our case studies. The second cally by the tool, (2) developed offline and given to Ter- column of the table characterises the complexity of the mite as part of the driver template, or (3) added or modi- two-player game constructed by the TSL compiler from fied by the user during an interactive code generation ses- 12
You can also read