Packages

Compile and load device tree overlays (DTBOs) at runtime on Nerves/Linux.

Current section

Files

Jump to
dts_buddy lib dts_buddy backend.ex
Raw

lib/dts_buddy/backend.ex

defmodule DtsBuddy.Backend do
@moduledoc """
Collection of functions that actually check system status, compile DTS files,
check overlay status.
"""
alias DtsBuddy.Paths
@doc """
Calls `mount -t configfs none /sys/kernel/config`.
"""
@spec enable_overlays() :: :ok | {:error, {any(), pos_integer()}}
def enable_overlays do
case System.cmd("mount", ["-t", "configfs", "none", Paths.config_dir()]) do
{_, 0} -> :ok
e -> {:error, e}
end
end
@doc """
Checks the presence of the `dtc` binary.
"""
@spec device_tree_compiler_present?() :: boolean()
def device_tree_compiler_present?, do: !is_nil(System.find_executable("dtc"))
@doc """
Checks the presence of the /sys/kernel/config/device-tree/overlays directory.
"""
@spec overlays_enabled?() :: boolean()
def overlays_enabled?, do: File.exists?(Paths.overlays_dir())
@doc """
Helper to display all system checks. Quite frugal for now, but should be extended.
"""
@spec checks() :: [
{:device_tree_compiler_present, boolean()} | {:overlays_enabled, boolean()}
]
def checks do
[
overlays_enabled: overlays_enabled?(),
device_tree_compiler_present: device_tree_compiler_present?()
]
end
@doc """
Loads an overlay. This function is meant to be directly called with the
output of DtsBuddy.compile/2 or DtsBuddy.compile_eex/3, that is, a tuple
of the form `{:error, term()}` or `{:ok, path, name}` where path is the
location of the compiled dtbo file, and name is the desired overlay name.
"""
@spec load({:error, any} | {:ok, binary(), binary()}) :: {:error, any()} | :ok
def load({:ok, path, name}), do: do_load(path, name)
def load({:error, e}), do: {:error, e}
@spec do_load(binary(), binary()) :: :ok | {:error, any()}
defp do_load(path, name) do
with :ok <- validate_name(name),
:ok <- File.mkdir_p(Paths.overlay_dir(name)),
{:ok, contents} <- File.read(path) do
File.write(Paths.dtbo_dir(name), contents)
end
end
@spec validate_name(binary()) :: :ok | {:error, {:invalid_name, any()}}
defp validate_name(name) do
case Paths.valid_name?(name) do
true -> :ok
false -> {:error, {:invalid_name, name}}
end
end
@doc """
Reads /sys/kernel/config/device-tree/overlays/<name>/status and gives the result
as `:applied` or `:unapplied`.
"""
@spec status(binary()) :: :applied | :unapplied
def status(name) do
case File.read(Paths.status_dir(name)) do
{:ok, "applied" <> _} -> :applied
_ -> :unapplied
end
end
@doc """
Injects an EEx template and compiles it.
"""
@spec compile_eex(binary(), keyword(), binary()) ::
{:error, {any(), pos_integer()}}
| {:ok, binary(), binary()}
def compile_eex(str, bindings, name) do
dts_string = EEx.eval_string(str, bindings)
compile(dts_string, "#{name}")
end
@spec do_compile(binary(), binary()) :: :ok | {:error, any()}
defp do_compile(dts, dtbo) do
case System.cmd("dtc", [
"-@",
"-I",
"dts",
"-O",
"dtb",
"-o",
dtbo,
dts
]) do
{_, 0} -> :ok
e -> {:error, e}
end
end
@doc """
Compiles a static DTS string to the given name.
Files written are /data/<name>.dts and /data/<name>.dtbo.
The name is used verbatim to build filesystem paths and is validated with
`DtsBuddy.Paths.valid_name?/1`; an invalid name returns
`{:error, {:invalid_name, name}}` without touching the filesystem.
"""
@spec compile(binary(), binary()) ::
{:error, any()}
| {:ok, binary(), binary()}
def compile(dts_string, name) do
with :ok <- validate_name(name),
dts_file = Paths.dts_file_path(name),
dtbo_file = Paths.dtbo_file_path(name),
{_, :ok} <- {:write, File.write(dts_file, dts_string)},
{_, :ok} <- {:compile, do_compile(dts_file, dtbo_file)} do
{:ok, dtbo_file, name}
else
{:error, {:invalid_name, _}} = e -> e
{:write, _} -> {:error, "Writing the DTS file failed. Please verify the name provided."}
{:compile, e} -> {:error, {"Compiling the DTS file failed. Here is output from DTC :", e}}
end
end
end