diff --git a/app/DTNode.stories.tsx b/app/DTNode.stories.tsx new file mode 100644 index 0000000..cf422c4 --- /dev/null +++ b/app/DTNode.stories.tsx @@ -0,0 +1,39 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { fn } from '@storybook/test'; +import { DataNode } from './DTNode'; + +const meta = { + title: 'App/DTNode', + component: DataNode, + argTypes: { + data: { + label: { control: 'text' }, + baseAddr: {}, + compat: {}, + }, + status: { control: 'radio', options: ['okay', 'disabled', undefined] }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Simple: Story = { + args: { + data: { + label: "UART", + baseAddr: "0x0c00_0000", + compat: "ns16550a", + }, + status: "okay", + }, +}; + +export const WithCompat: Story = { + args: { + data: { + label: "firmware", + compat: "raspberrypi,bcm2835-firmware", + }, + }, +}; diff --git a/app/DTNode.tsx b/app/DTNode.tsx index 5cdf221..89b081a 100644 --- a/app/DTNode.tsx +++ b/app/DTNode.tsx @@ -1,16 +1,134 @@ import { memo, useState } from "react"; import { Handle, NodeProps, Position } from "reactflow"; +import compatDb from "./compat-db.json"; +import genericNames from "./generic-names.json"; -const style = { - // wordWrap: "break-word", - whiteSpace: "pre-wrap" as "pre-wrap", // This is weird, TypoScripto... - padding: 4, - border: "2px solid", - background: "#0c0c0c", - color: "#fff", - width: 150, - fontSize: 11, - fontFamily: "Fira Code", +type DTStatus = "okay" | "disabled"; + +const dotColors: Record = { + okay: "blue", + disabled: "red" +}; + +export const Dot: FC<{ status?: DTStatus }> = ({ status }) => { + if (!status) { + return null; + } + const color = dotColors[status]; + return ( +
+ +
+ ); +}; + +const docsBaseUrl = "https://docs.kernel.org" +const drvBaseUrl = "https://elixir.bootlin.com/linux/HEAD/source/drivers"; +//const drvBaseUrl = "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers"; +const dtBaseUrl = "https://www.kernel.org/doc/Documentation/devicetree/bindings"; + +type DocsCategory = "binding" | "docs" | "driver"; + +type DocsEntry = { + category: DocsCategory; + path: string; +}; + +const getBaseUrl = (category: DocsCategory): string => { + switch(category) { + case "binding": return dtBaseUrl; + case "docs": return docsBaseUrl; + case "driver": return drvBaseUrl; + } +}; + +const getDocUrl = (compat: string) => { + const res = compat.split(";").find((c) => !!compatDb[c]); + if (!res) { + return null; + } + const d = compatDb[res]; + const baseUrl = getBaseUrl(d.category); + return `${baseUrl}/${d.path}`; +} + +const Compat: FC<{ compat?: string; }> = ({ compat }) => { + if (!compat) { + return null; + } + const docUrl = getDocUrl(compat); + + if (!docUrl) { + return compat; + } + + return ( + + {compat} + + + ); +}; + +export const DataNode: FC<{ data: object; status?: DTStatus }> = ({ + data, + status, +}) => { + const extraClass = genericNames.includes(data.label) ? "generic" : ""; + return ( +
+
{data.label}
+
+ {data.model} + {data.baseAddr} + + + {data.extra} +
+ +
+ ); }; const DTNode = ({ @@ -19,13 +137,7 @@ const DTNode = ({ targetPosition = Position.Top, sourcePosition = Position.Bottom }: NodeProps) => { - const [hovered, setHovered] = useState(false); - - const hoverOn = () => setHovered(true); - const hoverOff = () => setHovered(false); - - const borderColor = hovered ? "#987987" : "#789789"; - const borderStyle = hovered ? "dotted" : "solid"; + const { status, ...nData } = data; return ( <> -
- {data?.label} -
+ { return p ? p.join(", ") : null; }; +const getExtra = (n: DTNode) => { + if (n.name === "aliases" | n.name === "chosen") { + const ps = n.props.map((p) => { + const [k, v] = p; + return `${k}=${u8ArrToStr(v)}`; + }); + return ps.join("\n"); + } + return null; +}; + // transform a node's props into numbers and strings, omitting many const transformNode = (n: DTNode): DTNode => { const name = n.name || "root"; @@ -79,6 +91,9 @@ const transformNode = (n: DTNode): DTNode => { const clks = getProp(n, "clocks"); const cnames = getStringProp(n, "clock-names"); const compat = getStringProp(n, "compatible"); + const status = getStringProp(n, "status"); + const model = getStringProp(n, "model"); + const extra = getExtra(n); return { name, ...(phandle ? { phandle: phandle[0] } : null), @@ -89,6 +104,9 @@ const transformNode = (n: DTNode): DTNode => { ...(clks ? { clks } : null), ...(cnames ? { cnames } : null), ...(compat ? { compat } : null), + ...(status ? { status } : null), + ...(model ? { model } : null), + extra, }; }; @@ -100,7 +118,7 @@ export const transform = (n: DTNode, id: string = "10000") => { } }; -const NODE_WIDTH = 160; +const NODE_WIDTH = 260; const NODE_HEIGHT = 80; const weightedNode = (node: DTNode): DTNode => { @@ -139,18 +157,21 @@ export const getNodesEdges = (tree: DTNode) => { const nodes: TransformedNode[] = []; const edges: TransformedEdge[] = []; const rec = (n: DTNode, d: number = 1, baseX: number = 0, baseY: number = 0) => { - const [name, addr] = n.name.split("@"); + const { id, name, ...data } = n; + const [label, addr] = name.split("@"); const baseAddr = transformAddr(addr); nodes.push({ - id: n.id, + id, type: NodeType.custom, position: { x: baseX + n.size * NODE_WIDTH / 2, y: baseY + d * NODE_HEIGHT, }, data: { - label: `${name}\n${baseAddr}\n${n.size}`, + label, + baseAddr, + ...data, }, }); let offset = baseX;