1 | const pro = require("util").promisify;
|
2 | const fs = require("fs");
|
3 | const spawn = require("child_process").spawn;
|
4 | const codeGen = require("./code-gen.js");
|
5 | const rmDir = require("./rmdir.js");
|
6 | const Telnet = require("telnet-client");
|
7 |
|
8 | module.exports = async config => {
|
9 |
|
10 | return {
|
11 | async init(cli) {
|
12 | return cli.command("build")
|
13 | .option("-v, --verbose", "be verbose, display commands being run")
|
14 | .option("-d, --dependencies [dependencies]", "run 'npm install' to update dependencies prior to the build")
|
15 | .option("-a, --disassembly", "run 'objdump' to disassembly the image after build")
|
16 | .option("-s, --size", "run 'size' to display image size after build")
|
17 | .option("-f, --flash [port]", "flash the image using OpenOCD on given localhost port, defaults to 4444")
|
18 | .option("-l, --loop", "stay in loop and repeat build after each source file modification");
|
19 |
|
20 | },
|
21 |
|
22 | async start(command) {
|
23 |
|
24 | async function run(cmd, ...args) {
|
25 | if (command.verbose) {
|
26 | console.info(cmd, ...args);
|
27 | }
|
28 | return new Promise((resolve, reject) => {
|
29 | let proc = spawn(cmd, args, {
|
30 | stdio: "inherit"
|
31 | });
|
32 | proc.on("close", code => {
|
33 | if (code === 0) {
|
34 | resolve();
|
35 | } else {
|
36 | reject(`${cmd} returned code ${code}`);
|
37 | }
|
38 | });
|
39 | });
|
40 | }
|
41 |
|
42 | let watched;
|
43 |
|
44 | async function build() {
|
45 |
|
46 | watched = [];
|
47 | let buildDir = "build";
|
48 |
|
49 | await rmDir(buildDir, true);
|
50 | try {
|
51 | await pro(fs.mkdir)(buildDir);
|
52 | } catch (e) {
|
53 | if (e.code !== "EEXIST") {
|
54 | throw e;
|
55 | }
|
56 | }
|
57 |
|
58 | if (command.dependencies) {
|
59 | await run("npm", "install");
|
60 | }
|
61 |
|
62 | let packages = {};
|
63 |
|
64 | async function scan(directory) {
|
65 |
|
66 | if (command.verbose) {
|
67 | console.info("Scanning", directory);
|
68 | }
|
69 |
|
70 | let package = JSON.parse(await pro(fs.readFile)(directory + "/package.json", "utf8"));
|
71 | watched.push(directory + "/package.json");
|
72 |
|
73 | if (packages[package.name]) {
|
74 |
|
75 | if (package.version !== packages[package.name].version) {
|
76 | throw `${package.name} version conflict ${package.version} ${packages[package.name].version}`;
|
77 | }
|
78 |
|
79 | } else {
|
80 |
|
81 | package.directory = directory;
|
82 |
|
83 | for (let dep in package.dependencies) {
|
84 | await scan("./node_modules/" + dep);
|
85 | }
|
86 |
|
87 | packages[package.name] = package;
|
88 | }
|
89 |
|
90 | }
|
91 |
|
92 | await scan(".");
|
93 | let siliconPackages = Object.values(packages).filter(p => p.silicon);
|
94 |
|
95 | let target;
|
96 | siliconPackages.forEach(p => {
|
97 | if (p.silicon && p.silicon.target) {
|
98 | if (target) {
|
99 | throw "Target is defined twice";
|
100 | }
|
101 | target = p.silicon.target;
|
102 | }
|
103 | });
|
104 |
|
105 | let cpu = config.cpus[target.cpu];
|
106 | if (!cpu) {
|
107 | throw "Unsupported CPU: " + target.cpu;
|
108 | }
|
109 |
|
110 | function mapToArray(map, firstIndex = 0) {
|
111 | return Object.entries(map).reduce((acc, [k, v]) => {
|
112 | acc[parseInt(k) - firstIndex] = v;
|
113 | return acc;
|
114 | }, []);
|
115 | }
|
116 |
|
117 | let interrupts = mapToArray(cpu.interrupts || {}, 1).concat(
|
118 | mapToArray(
|
119 | siliconPackages.reduce((acc, p) => {
|
120 | Object.entries(p.silicon.interrupts || {}).forEach(([k, v]) => acc[k] = v);
|
121 | return acc;
|
122 | }, {})
|
123 | )
|
124 | );
|
125 |
|
126 |
|
127 | let siliconHpp = codeGen();
|
128 |
|
129 | siliconHpp.wl("#ifndef SILICON_HPP");
|
130 | siliconHpp.wl("#define SILICON_HPP");
|
131 |
|
132 | siliconHpp.wl();
|
133 | siliconHpp.wl("#include <stdlib.h>");
|
134 |
|
135 | siliconHpp.wl();
|
136 | siliconHpp.begin("namespace target {");
|
137 | siliconHpp.begin("namespace interrupts {");
|
138 | function writeInterrupts(kind, start, end) {
|
139 | siliconHpp.begin(`namespace ${kind} {`);
|
140 | for (let n = start; n < end; n++) {
|
141 | let name = interrupts[n];
|
142 | if (name) {
|
143 | siliconHpp.wl(`const int ${name} = ${n - start};`);
|
144 | }
|
145 | }
|
146 | siliconHpp.end("}");
|
147 | }
|
148 | writeInterrupts("Internal", 0, 15);
|
149 | writeInterrupts("External", 15, interrupts.length);
|
150 | writeInterrupts("All", 0, interrupts.length);
|
151 | siliconHpp.end("}");
|
152 | siliconHpp.end("}");
|
153 | siliconHpp.wl();
|
154 |
|
155 | function addIncludes(packages) {
|
156 | packages.forEach(p => {
|
157 | (p.silicon.sources || []).forEach(s => {
|
158 | siliconHpp.wl(`#include "../${p.directory}/${s}"`);
|
159 | watched.push(`${p.directory}/${s}`);
|
160 | });
|
161 | });
|
162 | }
|
163 |
|
164 | addIncludes(siliconPackages.filter(p => p.silicon.target));
|
165 | addIncludes(siliconPackages.filter(p => !p.silicon.target));
|
166 |
|
167 | siliconHpp.wl();
|
168 | siliconHpp.wl("#endif // SILICON_HPP");
|
169 |
|
170 | let siliconHppFile = "build/silicon.hpp";
|
171 | await siliconHpp.toFile(siliconHppFile);
|
172 |
|
173 | let siliconCpp = codeGen();
|
174 | siliconCpp.wl("#include \"silicon.hpp\"");
|
175 | let siliconCppFile = "build/silicon.cpp";
|
176 | await siliconHpp.toFile(siliconCppFile);
|
177 |
|
178 | let interruptsSFile = "build/interrupts.S";
|
179 | let interruptsS = codeGen();
|
180 | interruptsS.wl(`.section .text`);
|
181 | interruptsS.wl(`.weak fatalError`);
|
182 | interruptsS.wl(`fatalError:`);
|
183 | interruptsS.wl(`b fatalError`);
|
184 |
|
185 |
|
186 | interruptsS.wl(`.section .interrupts`);
|
187 |
|
188 | for (let i = 0; i < interrupts.length; i++) {
|
189 | let interrupt = interrupts[i];
|
190 | let handler = "interruptHandler" + interrupt;
|
191 | handler = "_Z" + handler.length + handler + "v";
|
192 | if (interrupt) {
|
193 | interruptsS.wl(`.weak ${handler}`);
|
194 | interruptsS.wl(`.set ${handler}, fatalError`);
|
195 | interruptsS.wl(`.word ${handler} + 1`);
|
196 | } else {
|
197 | interruptsS.wl(".word fatalError + 1");
|
198 | }
|
199 | }
|
200 |
|
201 | interruptsS.toFile(interruptsSFile);
|
202 |
|
203 | let imageFile = "build/build.elf";
|
204 |
|
205 | let gccParams = [
|
206 | "-I", "build",
|
207 | "-T", cpu.ldScript,
|
208 | "-nostartfiles",
|
209 | "--specs=nano.specs",
|
210 | "-fshort-wchar",
|
211 | "-g",
|
212 | "-Og",
|
213 | "-std=c++14",
|
214 | "-fno-rtti",
|
215 | "-fno-exceptions",
|
216 | "-ffunction-sections",
|
217 | "-fdata-sections",
|
218 | ...cpu.gccParams,
|
219 | ...siliconPackages.reduce((acc, p) => {
|
220 | return acc.concat(Object.entries(p.silicon.symbols || {}).map(([k, v]) => `-Wl,--defsym,${k}=${v}`));
|
221 | }, []),
|
222 | "-o", imageFile,
|
223 | interruptsSFile,
|
224 | cpu.startS,
|
225 | siliconCppFile
|
226 | ];
|
227 |
|
228 | await run(cpu.gccPrefix + "gcc", ...gccParams);
|
229 |
|
230 | if (command.disassembly) {
|
231 | await run(cpu.gccPrefix + "objdump", "--section=.text", "-D", imageFile);
|
232 | }
|
233 |
|
234 | if (command.size) {
|
235 | await run(cpu.gccPrefix + "size", imageFile);
|
236 | }
|
237 |
|
238 | if (command.flash) {
|
239 |
|
240 | let connection = new Telnet();
|
241 | let port = command.flash === true ? 4444 : parseInt(command.flash);
|
242 |
|
243 | await connection.connect({
|
244 | port,
|
245 | shellPrompt: "> ",
|
246 | debug: true
|
247 | });
|
248 |
|
249 | async function exec(cmd) {
|
250 | let res = await connection.exec(cmd);
|
251 | console.info(res);
|
252 | return res;
|
253 | }
|
254 |
|
255 | await exec("reset halt");
|
256 | await exec("flash write_image erase " + process.cwd() + "/" + imageFile);
|
257 | await exec("reset run");
|
258 |
|
259 | await connection.end();
|
260 |
|
261 | }
|
262 | }
|
263 |
|
264 | async function watchedChanged() {
|
265 | return new Promise((resolve, reject) => {
|
266 | console.info("Waiting for a source file change...");
|
267 | let watchers = [];
|
268 | watched.forEach(file => {
|
269 | watchers.push(fs.watch(file, () => {
|
270 | watchers.forEach(w => w.close());
|
271 | console.info(file, "changed");
|
272 | resolve();
|
273 | }));
|
274 | });
|
275 | });
|
276 | }
|
277 | ;
|
278 |
|
279 | do {
|
280 | try {
|
281 | await build();
|
282 | } catch (e) {
|
283 | if (command.loop) {
|
284 | console.error(e);
|
285 | } else {
|
286 | throw e;
|
287 | }
|
288 | }
|
289 | if (!command.loop) {
|
290 | break;
|
291 | }
|
292 | await watchedChanged();
|
293 | } while (true);
|
294 |
|
295 | }
|
296 | };
|
297 |
|
298 | }; |
\ | No newline at end of file |