diff options
| author | Wu, Zhenyu <[email protected]> | 2024-12-13 00:37:51 +0800 |
|---|---|---|
| committer | Wu, Zhenyu <[email protected]> | 2024-12-13 00:37:51 +0800 |
| commit | ecb1e9948ed95fbe8c5b43dce118718449cff93d (patch) | |
| tree | 197a7db6d63919b91071758bebd9b22f49fd5f9e /xmake/rules/platform/linux/module/driver_modules.lua | |
| parent | 3c99013d8f3f465616918f21e0e2e6deb701f9f3 (diff) | |
rename luarocks.module to lua.module, platform.linux.driver to platform.linux.module
Diffstat (limited to 'xmake/rules/platform/linux/module/driver_modules.lua')
| -rw-r--r-- | xmake/rules/platform/linux/module/driver_modules.lua | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/xmake/rules/platform/linux/module/driver_modules.lua b/xmake/rules/platform/linux/module/driver_modules.lua new file mode 100644 index 000000000..8218a972c --- /dev/null +++ b/xmake/rules/platform/linux/module/driver_modules.lua @@ -0,0 +1,326 @@ +--!A cross-platform build utility based on Lua +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- +-- Copyright (C) 2015-present, TBOOX Open Source Group. +-- +-- @author ruki +-- @file driver_modules.lua +-- + +-- imports +import("core.base.option") +import("core.project.depend") +import("core.cache.memcache") +import("lib.detect.find_tool") +import("utils.progress") + +-- get linux-headers sdk +function _get_linux_headers_sdk(target) + local linux_headersdir = target:values("linux.driver.linux-headers") + local linux_builddir = target:values("linux.driver.linux-builddir") + if linux_headersdir then + return { + sdkdir = linux_headersdir, + builddir = linux_builddir, + includedir = path.join(linux_headersdir, "include") + } + end + local linux_headers = assert(target:pkg("linux-headers"), "please add `add_requires(\"linux-headers\", {configs = {driver_modules = true}})` and `add_packages(\"linux-headers\")` to the given target!") + local includedirs = linux_headers:get("includedirs") or linux_headers:get("sysincludedirs") + local version = linux_headers:version() + local includedir + for _, dir in ipairs(includedirs) do + if dir:find("linux-headers", 1, true) then + includedir = dir + linux_headersdir = path.directory(dir) + break + end + end + assert(linux_headersdir, "linux-headers not found!") + if not os.isfile(path.join(includedir, "generated/autoconf.h")) and + not os.isfile(path.join(includedir, "config/auto.conf")) then + raise("kernel configuration is invalid. include/generated/autoconf.h or include/config/auto.conf are missing.") + end + return {version = version, sdkdir = linux_headersdir, includedir = includedir} +end + +-- get cflags from make +function _get_cflags_from_make(target, sdkdir, builddir) + local key = sdkdir .. target:arch() + local cflags = memcache.get2("linux.driver", key, "cflags") + local ldflags_o = memcache.get2("linux.driver", key, "ldflags_o") + local ldflags_ko = memcache.get2("linux.driver", key, "ldflags_ko") + if cflags == nil then + local make = assert(find_tool("make"), "make not found!") + local tmpdir = os.tmpfile() .. ".dir" + local makefile = path.join(tmpdir, "Makefile") + local stubfile = path.join(tmpdir, "src/stub.c") + local foofile = path.join(tmpdir, "src/foo.c") + io.writefile(makefile, [[obj-m := stub.o +stub-objs := src/stub.o src/foo.o]]) + io.writefile(foofile, "") + io.writefile(stubfile, [[ +#include <linux/init.h> +#include <linux/module.h> + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_AUTHOR("Ruki"); +MODULE_DESCRIPTION("A simple Hello World Module"); +MODULE_ALIAS("a simplest module"); + +static int hello_init(void) { + printk(KERN_INFO "Hello World\n"); + return 0; +} + +static void hello_exit(void) { + printk(KERN_INFO "Goodbye World\n"); +} + +module_init(hello_init); +module_exit(hello_exit); + ]]) + local argv = {"-C", sdkdir, "V=1", "M=" .. tmpdir, "modules"} + if builddir then + table.insert(argv, "O=" .. builddir) + end + if not target:is_plat(os.subhost()) then + -- e.g. $(MAKE) -C $(KERN_DIR) V=1 ARCH=arm64 CROSS_COMPILE=/mnt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu- M=$(PWD) modules + local arch + if target:is_arch("arm", "armv7") then + arch = "arm" + elseif target:is_arch("arm64", "arm64-v8a") then + arch = "arm64" + elseif target:is_arch("mips") then + arch = "mips" + elseif target:is_arch("ppc", "ppc64", "powerpc", "powerpc64") then + arch = "powerpc" + end + assert(arch, "unknown arch(%s)!", target:arch()) + local cc = target:tool("cc") + local cross = cc:gsub("%-gcc$", "-") + table.insert(argv, "ARCH=" .. arch) + table.insert(argv, "CROSS_COMPILE=" .. cross) + end + local result, errors = try {function () return os.iorunv(make.program, argv, {curdir = tmpdir}) end} + if result then + -- we can also split ';' for the muliple commands + for _, line in ipairs(result:split("[\n;]")) do + line = line:trim() + if line:endswith("stub.c") then + local include_cflag = false + for _, cflag in ipairs(line:split("%s+")) do + local has_cflag = false + if cflag:startswith("-fplugin=") then + -- @see https://github.com/xmake-io/xmake/issues/3279 + local plugindir = cflag:sub(10) + if not path.is_absolute(plugindir) then + plugindir = path.absolute(plugindir, sdkdir) + end + cflag = "-fplugin=" .. plugindir + has_cflag = true + elseif cflag:startswith("-f") or cflag:startswith("-m") + or (cflag:startswith("-W") and not cflag:startswith("-Wp,-MMD,") and not cflag:startswith("-Wp,-MD,")) + or (cflag:startswith("-D") and not cflag:find("KBUILD_MODNAME=") and not cflag:find("KBUILD_BASENAME=")) then + has_cflag = true + local macro = cflag:match("%-D\"(.+)\"") -- -D"KBUILD_XXX=xxx" + if macro then + cflag = "-D" .. macro + end + elseif cflag == "-I" or cflag == "-isystem" or cflag == "-include" then + include_cflag = cflag + elseif cflag:startswith("-I") or include_cflag then + local includedir = cflag + if cflag:startswith("-I") then + includedir = cflag:sub(3) + end + if not path.is_absolute(includedir) then + includedir = path.absolute(includedir, builddir or sdkdir) + end + if cflag:startswith("-I") then + cflag = "-I" .. includedir + else + cflag = include_cflag .. " " .. includedir + end + has_cflag = true + include_cflag = nil + end + if has_cflag then + cflags = cflags or {} + table.insert(cflags, cflag) + end + end + end + local ldflags = line:match("%-ld (.+) %-o ") or line:match("ld (.+) %-o ") + if ldflags then + local ko = ldflags:find("-T ", 1, true) + for _, ldflag in ipairs(os.argv(ldflags)) do + if ldflag:endswith(".lds") then + if not path.is_absolute(ldflag) then + ldflag = path.absolute(ldflag, builddir or sdkdir) + end + end + if ko then + -- e.g. aarch64-linux-gnu-ld -r -EL -maarch64elf --build-id=sha1 -T scripts/module.lds -o hello.ko hello.o hello.mod.o + ldflags_ko = ldflags_ko or {} + table.insert(ldflags_ko, ldflag) + else + -- e.g. aarch64-linux-gnu-ld -EL -maarch64elf -r -o hello.o xxx.o + ldflags_o = ldflags_o or {} + table.insert(ldflags_o, ldflag) + end + end + end + if cflags and ldflags_o and ldflags_ko then + break + end + end + else + if option.get("diagnosis") then + print("rule(platform.linux.driver): cannot get cflags from make!") + print(errors) + end + end + os.tryrm(tmpdir) + memcache.set2("linux.driver", key, "cflags", cflags or false) + memcache.set2("linux.driver", key, "ldflags_o", ldflags_o or false) + memcache.set2("linux.driver", key, "ldflags_ko", ldflags_ko or false) + end + return cflags or nil, ldflags_o or nil, ldflags_ko or nil +end + +function load(target) + -- we only need binary kind, because we will rewrite on_link + target:set("kind", "binary") + target:set("extension", ".ko") +end + +function config(target) + + -- get and save linux-headers sdk + local linux_headers = _get_linux_headers_sdk(target) + target:data_set("linux.driver.linux_headers", linux_headers) + + -- check compiler, we must use gcc + assert(target:has_tool("cc", "gcc"), "we must use gcc compiler!") + + -- check rules + for _, rulename in ipairs({"mode.release", "mode.debug", "mode.releasedbg", "mode.minsizerel", "mode.asan", "mode.tsan"}) do + assert(not target:rule(rulename), "target(%s) is linux driver module, it need not rule(%s)!", target:name(), rulename) + end + + -- we need to disable includedirs from add_packages("linux-headers") + if target:pkg("linux-headers") then + target:pkg("linux-headers"):set("includedirs", nil) + target:pkg("linux-headers"):set("sysincludedirs", nil) + end + + -- add compilation flags + target:add("defines", "KBUILD_MODNAME=\"" .. target:name() .. "\"") + for _, sourcefile in ipairs(target:sourcefiles()) do + target:fileconfig_set(sourcefile, {defines = "KBUILD_BASENAME=\"" .. path.basename(sourcefile) .. "\""}) + end + local cflags, ldflags_o, ldflags_ko = _get_cflags_from_make(target, linux_headers.sdkdir, linux_headers.builddir) + if cflags then + target:add("cflags", cflags, {force = true}) + target:data_set("linux.driver.ldflags_o", ldflags_o) + target:data_set("linux.driver.ldflags_ko", ldflags_ko) + end +end + +function link(target, opt) + local targetfile = target:targetfile() + local dependfile = target:dependfile(targetfile) + local objectfiles = target:objectfiles() + depend.on_changed(function () + + -- trace + progress.show(opt.progress, "${color.build.object}linking.$(mode) %s", targetfile) + + -- get module scripts + local modpost + local linux_headers = target:data("linux.driver.linux_headers") + if linux_headers then + modpost = path.join(linux_headers.builddir or linux_headers.sdkdir, "scripts", "mod", "modpost") + end + assert(modpost and os.isfile(modpost), "scripts/mod/modpost not found!") + + -- get ld + local ld = target:tool("ld") + assert(ld, "ld not found!") + ld = ld:gsub("gcc$", "ld") + ld = ld:gsub("g%+%+$", "ld") + + -- link target.o + local argv = {} + local ldflags_o = target:data("linux.driver.ldflags_o") + if ldflags_o then + table.join2(argv, ldflags_o) + end + local targetfile_o = target:objectfile(targetfile) + table.join2(argv, "-o", targetfile_o) + table.join2(argv, objectfiles) + os.mkdir(path.directory(targetfile_o)) + os.vrunv(ld, argv) + + -- generate target.mod + local targetfile_mod = targetfile_o:gsub("%.o$", ".mod") + io.writefile(targetfile_mod, table.concat(objectfiles, "\n") .. "\n\n") + + -- generate .sourcename.o.cmd + -- we only need to touch an empty file, otherwise modpost command will raise error. + for _, objectfile in ipairs(objectfiles) do + local objectdir = path.directory(objectfile) + local objectname = path.filename(objectfile) + local cmdfile = path.join(objectdir, "." .. objectname .. ".cmd") + io.writefile(cmdfile, "") + end + + -- generate target.mod.c + local orderfile = path.join(path.directory(targetfile_o), "modules.order") + local symversfile = path.join(path.directory(targetfile_o), "Module.symvers") + argv = {"-m", "-a", "-o", symversfile, "-e", "-N", "-w", "-T", orderfile} + io.writefile(orderfile, targetfile_o .. "\n") + os.vrunv(modpost, argv) + + -- compile target.mod.c + local targetfile_mod_c = targetfile_o:gsub("%.o$", ".mod.c") + local targetfile_mod_o = targetfile_o:gsub("%.o$", ".mod.o") + local compinst = target:compiler("cc") + if option.get("verbose") then + print(compinst:compcmd(targetfile_mod_c, targetfile_mod_o, {target = target, rawargs = true})) + end + assert(compinst:compile(targetfile_mod_c, targetfile_mod_o, {target = target})) + + -- link target.ko + argv = {} + local ldflags_ko = target:data("linux.driver.ldflags_ko") + if ldflags_ko then + table.join2(argv, ldflags_ko) + end + local targetfile_o = target:objectfile(targetfile) + table.join2(argv, "-o", targetfile, targetfile_o, targetfile_mod_o) + os.mkdir(path.directory(targetfile)) + os.vrunv(ld, argv) + + end, {dependfile = dependfile, lastmtime = os.mtime(target:targetfile()), files = objectfiles, changed = target:is_rebuilt()}) +end + +function install(target) + os.vrunv("insmod", {target:targetfile()}) +end + +function uninstall(target) + os.vrunv("rmmod", {target:targetfile()}) +end |
