@k13engineering/linux-dmabuf 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ import type { TMemoryMappedBuffer } from "@k13engineering/po6-mmap";
2
+ type TRefcountedBufferMapping = Uint8Array & {
3
+ release: () => void;
4
+ };
5
+ declare const createCachedMapper: ({ map, }: {
6
+ map: () => TMemoryMappedBuffer;
7
+ }) => {
8
+ maybeMap: () => TRefcountedBufferMapping;
9
+ mapped: () => boolean;
10
+ close: () => void;
11
+ };
12
+ export { createCachedMapper };
@@ -0,0 +1,74 @@
1
+ /*import type { TMemoryMappedBuffer } from "@k13engineering/po6-mmap";*/
2
+
3
+ /*type TRefcountedBufferMapping = Uint8Array & {
4
+ release: () => void;
5
+ };*/
6
+
7
+ const createCachedMapper = ({
8
+ map,
9
+ }/*: {
10
+ map: () => TMemoryMappedBuffer;
11
+ }*/) => {
12
+
13
+ let refcount = 0;
14
+ let mappedBuffer/*: TMemoryMappedBuffer | undefined*/ = undefined;
15
+
16
+ const maybeMap = ()/*: TRefcountedBufferMapping*/ => {
17
+ if (mappedBuffer === undefined) {
18
+ mappedBuffer = map();
19
+ }
20
+
21
+ refcount += 1;
22
+ let released = false;
23
+
24
+ // eslint-disable-next-line complexity
25
+ const release = () => {
26
+ if (released) {
27
+ throw Error("handle already released");
28
+ }
29
+
30
+ if (refcount <= 0) {
31
+ throw Error("BUG: release called when refcount is <= 0");
32
+ }
33
+
34
+ if (mappedBuffer === undefined) {
35
+ throw Error("BUG: release called but mappedBuffer is undefined");
36
+ }
37
+
38
+ refcount -= 1;
39
+
40
+ if (refcount === 0) {
41
+ mappedBuffer.unmap();
42
+ mappedBuffer = undefined;
43
+ }
44
+
45
+ released = true;
46
+ };
47
+
48
+ const bufferView = new Uint8Array(mappedBuffer.buffer, mappedBuffer.byteOffset, mappedBuffer.byteLength);
49
+ const cachedMappedBuffer = bufferView /*as TRefcountedBufferMapping*/;
50
+
51
+ // monkey-patch mappingId and release method
52
+ cachedMappedBuffer.release = release;
53
+
54
+ return cachedMappedBuffer;
55
+ };
56
+
57
+ const mapped = () => {
58
+ return mappedBuffer !== undefined;
59
+ };
60
+
61
+ const close = () => {
62
+
63
+ };
64
+
65
+ return {
66
+ maybeMap,
67
+ mapped,
68
+ close
69
+ };
70
+ };
71
+
72
+ export {
73
+ createCachedMapper
74
+ };
@@ -0,0 +1,44 @@
1
+ import { type TDmabufMapping, type TDmabufMappingAccess } from "./dmabuf-mapping.js";
2
+ import type { TLinuxDmabufInterface } from "./linux-interface.js";
3
+ type TDmabufInfo = {
4
+ inode: number;
5
+ size: number;
6
+ };
7
+ type TDmabufSync = {
8
+ end: () => void;
9
+ };
10
+ type TSyncReadOrWrite = {
11
+ read: true;
12
+ write: false;
13
+ } | {
14
+ read: false;
15
+ write: true;
16
+ } | {
17
+ read: true;
18
+ write: true;
19
+ };
20
+ type TDmabufHandle = {
21
+ exportAndDupAsDmabufFd: () => {
22
+ dmabufFd: number;
23
+ };
24
+ info: () => TDmabufInfo;
25
+ map: (args: {
26
+ iKnowWhatImDoing: boolean;
27
+ access: TDmabufMappingAccess;
28
+ }) => TDmabufMapping;
29
+ sync: (args: {
30
+ iKnowWhatImDoing: boolean;
31
+ } & TSyncReadOrWrite) => TDmabufSync;
32
+ close: () => void;
33
+ };
34
+ declare const createHandleImporter: ({ linuxInterface }: {
35
+ linuxInterface: TLinuxDmabufInterface;
36
+ }) => {
37
+ importAndDupDmabuf: ({ dmabufFd: providedDmabufFd }: {
38
+ dmabufFd: number;
39
+ }) => TDmabufHandle;
40
+ };
41
+ type THandleImporter = ReturnType<typeof createHandleImporter>;
42
+ type TImportAndDupDmabufFunc = THandleImporter["importAndDupDmabuf"];
43
+ export { createHandleImporter };
44
+ export type { TDmabufHandle, TSyncReadOrWrite, TDmabufSync, TImportAndDupDmabufFunc };
@@ -0,0 +1,282 @@
1
+
2
+ import { /*type TMemoryProtectionFlags */} from "@k13engineering/po6-mmap";
3
+ import { createMappingHelper, /*type TDmabufMapping, *//*type TDmabufMappingAccess */} from "./dmabuf-mapping.js";
4
+ import { createDefaultGarbageCollectedWithoutReleaseError, createGarbageCollectionGuard } from "./snippets/gc-guard.js";
5
+ /*import type { TLinuxDmabufInterface } from "./linux-interface.ts";*/
6
+
7
+ /*type TDmabufInfo = {
8
+ inode: number;
9
+ size: number;
10
+ };*/
11
+
12
+ /*type TDmabufSync = {
13
+ end: () => void;
14
+ };*/
15
+
16
+ /*type TSyncReadOrWrite = {
17
+ read: true;
18
+ write: false;
19
+ } | {
20
+ read: false;
21
+ write: true;
22
+ } | {
23
+ read: true;
24
+ write: true;
25
+ };*/
26
+
27
+ /*type TDmabufHandleInfo = {
28
+ handleId: number;
29
+ inode: number;
30
+ size: number;
31
+ };*/
32
+
33
+ /*type TDmabufHandle = {
34
+ exportAndDupAsDmabufFd: () => { dmabufFd: number };
35
+ info: () => TDmabufInfo;
36
+ map: (args: { iKnowWhatImDoing: boolean, access: TDmabufMappingAccess }) => TDmabufMapping;
37
+ sync: (args: { iKnowWhatImDoing: boolean } & TSyncReadOrWrite) => TDmabufSync;
38
+ close: () => void;
39
+ };*/
40
+
41
+ const dmabufGarbageCollectionGuard = createGarbageCollectionGuard({
42
+ createError: ({ info }/*: { info: TDmabufHandleInfo }*/) => {
43
+ return createDefaultGarbageCollectedWithoutReleaseError({
44
+ name: "DmabufMappingGarbageCollectedWithoutReleaseError",
45
+ info: `dma buffer handle with handleId=${info.handleId} inode=${info.inode} size=${info.size}`,
46
+ releaseFunctionName: "release",
47
+ resourcesName: "dma buffer handles",
48
+ });
49
+ }
50
+ });
51
+
52
+ let handleIdCounter = 0;
53
+
54
+
55
+ const createHandleImporter = ({
56
+ linuxInterface
57
+ }/*: {
58
+ linuxInterface: TLinuxDmabufInterface
59
+ }*/) => {
60
+
61
+
62
+ // eslint-disable-next-line max-statements
63
+ const importAndDupDmabuf = ({ dmabufFd: providedDmabufFd }/*: { dmabufFd: number }*/)/*: TDmabufHandle*/ => {
64
+
65
+ const assertIsADmaBuf = ({ dmabufFd }/*: { dmabufFd: number }*/)/*: void*/ => {
66
+ if (dmabufFd < 0) {
67
+ throw Error(`invalid dmabuf fd ${dmabufFd}`);
68
+ }
69
+
70
+ try {
71
+ linuxInterface.fstat({ fd: dmabufFd });
72
+ } catch (ex) {
73
+ throw Error(`invalid dmabuf fd ${dmabufFd}, fstat failed`, { cause: ex /*as Error*/ });
74
+ }
75
+
76
+ try {
77
+ linuxInterface.dmabufIoctlSyncStart({ dmabufFd, read: true, write: false });
78
+ linuxInterface.dmabufIoctlSyncEnd({ dmabufFd, read: true, write: false });
79
+ } catch (ex) {
80
+ throw Error(`fd ${dmabufFd} is not a valid dmabuf fd, ioctl check failed`, { cause: ex /*as Error*/ });
81
+ }
82
+ };
83
+
84
+ assertIsADmaBuf({ dmabufFd: providedDmabufFd });
85
+ const dmabufFd = linuxInterface.dup({ fd: providedDmabufFd });
86
+
87
+ let closed = false;
88
+
89
+ const st = linuxInterface.fstat({ fd: dmabufFd });
90
+ const inode = st.inode;
91
+
92
+ const assertNotClosed = () => {
93
+ if (closed) {
94
+ throw Error(`dmabuf handle already closed`);
95
+ }
96
+ };
97
+
98
+ const assertFdIsUnchanged = () => {
99
+ // try {
100
+ // assertIsADmaBuf({ dmabufFd });
101
+ // } catch (e) {
102
+ // let message = `internal dmabuf handle ${dmabufFd} (inode ${inode}) is no longer a valid dmabuf fd,`;
103
+ // message += ` probably someone closed a wrong file descriptor`;
104
+ // message += ` - your program is probably in an inconsistent state`;
105
+ // throw Error(message, { cause: e as Error });
106
+ // }
107
+
108
+ const currentSt = linuxInterface.fstat({ fd: dmabufFd });
109
+ if (currentSt.inode !== inode) {
110
+ let message = `internal dmabuf handle ${dmabufFd} (inode ${inode}) fd's inode has changed to ${currentSt.inode},`;
111
+ message += ` probably someone closed a wrong file descriptor and the OS reused the fd for a different file`;
112
+ message += ` - your program is probably in an inconsistent state`;
113
+ throw Error(message);
114
+ }
115
+ };
116
+
117
+ const exportAndDupAsDmabufFd/*: TDmabufHandle["exportAndDupAsDmabufFd"]*/ = () => {
118
+ assertNotClosed();
119
+ assertFdIsUnchanged();
120
+
121
+ const newFd = linuxInterface.dup({ fd: dmabufFd });
122
+
123
+ return {
124
+ dmabufFd: newFd
125
+ };
126
+ };
127
+
128
+ const info/*: TDmabufHandle["info"]*/ = () => {
129
+ assertNotClosed();
130
+ assertFdIsUnchanged();
131
+
132
+ const stat = linuxInterface.fstat({ fd: dmabufFd });
133
+
134
+ const dmabufInfo/*: TDmabufInfo*/ = {
135
+ inode: stat.inode,
136
+ size: stat.size
137
+ };
138
+
139
+ return dmabufInfo;
140
+ };
141
+
142
+ // type TMapping = {
143
+ // buffer: TMemoryMappedBuffer;
144
+ // access: TMapAccess;
145
+ // };
146
+
147
+ const mapAsserted = ({ memoryProtectionFlags }/*: { memoryProtectionFlags: TMemoryProtectionFlags }*/) => {
148
+ const { size } = info();
149
+
150
+ const { errno, buffer } = linuxInterface.mmapFd({
151
+ fd: dmabufFd,
152
+ mappingVisibility: "MAP_SHARED",
153
+ memoryProtectionFlags,
154
+ genericFlags: {},
155
+ offsetInFd: 0,
156
+ length: size
157
+ });
158
+
159
+ if (errno !== undefined) {
160
+ throw Error(`dmabuf mmap failed with errno ${errno}`);
161
+ }
162
+
163
+ return buffer;
164
+ };
165
+
166
+ const mappingHelper = createMappingHelper({
167
+ mapAsserted
168
+ });
169
+
170
+ const map/*: TDmabufHandle["map"]*/ = ({ iKnowWhatImDoing, access }) => {
171
+ if (!iKnowWhatImDoing) {
172
+ let message = `mapping dmabufs is for internal or advanced usage only:`;
173
+ message += ` dmabuf accesses need proper synchronization;`;
174
+ message += ` accessing the mapped memory after close can cause program crashes or data corruption`;
175
+ message += ` if you really want to do this, please set iKnowWhatImDoing to true`;
176
+
177
+ throw Error(message);
178
+ }
179
+
180
+ assertNotClosed();
181
+ assertFdIsUnchanged();
182
+
183
+ const mapping = mappingHelper.map({ access });
184
+
185
+ return mapping;
186
+ };
187
+
188
+ let syncStarted = false;
189
+
190
+ const sync/*: TDmabufHandle["sync"]*/ = ({ iKnowWhatImDoing, read, write }) => {
191
+ if (!iKnowWhatImDoing) {
192
+ let message = `creating dmabuf sync is for internal or advanced usage only:`;
193
+ message += ` start / end calls must be properly paired;`;
194
+ message += ` if you really want to do this, please set iKnowWhatImDoing to true`;
195
+
196
+ throw Error(message);
197
+ }
198
+
199
+ assertNotClosed();
200
+ assertFdIsUnchanged();
201
+
202
+ if (syncStarted) {
203
+ throw Error(`dmabuf sync already started, make sure to call end() before starting a new sync`);
204
+ }
205
+
206
+ linuxInterface.dmabufIoctlSyncStart({ dmabufFd, read, write });
207
+ syncStarted = true;
208
+
209
+ let stale = false;
210
+
211
+ const end = () => {
212
+ assertNotClosed();
213
+ assertFdIsUnchanged();
214
+
215
+ if (stale) {
216
+ throw Error(`stale sync instance, end() already called`);
217
+ }
218
+
219
+ linuxInterface.dmabufIoctlSyncEnd({ dmabufFd, read: true, write: true });
220
+ syncStarted = false;
221
+ stale = true;
222
+ };
223
+
224
+ return {
225
+ end
226
+ };
227
+ };
228
+
229
+ const close/*: TDmabufHandle["close"]*/ = () => {
230
+ assertNotClosed();
231
+ assertFdIsUnchanged();
232
+
233
+ if (syncStarted) {
234
+ throw Error(`dmabuf sync started but not ended, please end sync before closing the handle`);
235
+ }
236
+
237
+ linuxInterface.close({ fd: dmabufFd });
238
+ closed = true;
239
+ };
240
+
241
+ const handleId = handleIdCounter;
242
+ handleIdCounter += 1;
243
+
244
+ const { release: protectedClose } = dmabufGarbageCollectionGuard.protect({
245
+ release: () => {
246
+ close();
247
+ },
248
+
249
+ info: {
250
+ handleId,
251
+ inode,
252
+ size: info().size
253
+ }
254
+ });
255
+
256
+ return {
257
+ exportAndDupAsDmabufFd,
258
+ info,
259
+ map,
260
+ sync,
261
+ close: protectedClose
262
+ };
263
+ };
264
+
265
+ return {
266
+ importAndDupDmabuf
267
+ };
268
+ };
269
+
270
+ /*type THandleImporter = ReturnType<typeof createHandleImporter>;*/
271
+ /*type TImportAndDupDmabufFunc = THandleImporter["importAndDupDmabuf"];*/
272
+
273
+ export {
274
+ createHandleImporter
275
+ };
276
+
277
+ /*export type {
278
+ TDmabufHandle,
279
+ TSyncReadOrWrite,
280
+ TDmabufSync,
281
+ TImportAndDupDmabufFunc
282
+ };*/
@@ -0,0 +1,11 @@
1
+ declare const dmabufIoctlSyncStart: ({ dmabufFd, read, write }: {
2
+ dmabufFd: number;
3
+ read: boolean;
4
+ write: boolean;
5
+ }) => void;
6
+ declare const dmabufIoctlSyncEnd: ({ dmabufFd, read, write }: {
7
+ dmabufFd: number;
8
+ read: boolean;
9
+ write: boolean;
10
+ }) => void;
11
+ export { dmabufIoctlSyncStart, dmabufIoctlSyncEnd };
@@ -0,0 +1,67 @@
1
+ import { ioctl } from "@k13engineering/po6-ioctl";
2
+ import nodeOs from "node:os";
3
+
4
+ const endianness = nodeOs.endianness();
5
+
6
+ const flagsToFlagsBuffer = ({ flags }/*: { flags: bigint }*/) => {
7
+ const flagBuffer = Buffer.alloc(8);
8
+ if (endianness === "LE") {
9
+ flagBuffer.writeBigUInt64LE(flags, 0);
10
+ } else {
11
+ flagBuffer.writeBigUInt64BE(flags, 0);
12
+ }
13
+ return flagBuffer;
14
+ };
15
+
16
+ const DMA_BUF_IOCTL_SYNC = BigInt(0x40086200);
17
+
18
+ const DMA_BUF_SYNC_READ = BigInt(0x1);
19
+ const DMA_BUF_SYNC_WRITE = BigInt(0x2);
20
+ const DMA_BUF_SYNC_START = BigInt(0x00);
21
+ const DMA_BUF_SYNC_END = BigInt(0x4);
22
+
23
+ const dmabufIoctlSync = ({ dmabufFd, flags }/*: { dmabufFd: number, flags: bigint }*/) => {
24
+
25
+ const flagBuffer = flagsToFlagsBuffer({ flags });
26
+
27
+ const { errno } = ioctl({
28
+ fd: dmabufFd,
29
+ request: DMA_BUF_IOCTL_SYNC,
30
+ arg: flagBuffer
31
+ });
32
+
33
+ if (errno !== undefined) {
34
+ throw Error(`dmabuf ioctl sync start failed with errno ${errno}`);
35
+ }
36
+ };
37
+
38
+ const dmabufIoctlSyncStart = ({ dmabufFd, read, write }/*: { dmabufFd: number, read: boolean; write: boolean }*/) => {
39
+
40
+ let flags = DMA_BUF_SYNC_START;
41
+ if (read) {
42
+ flags |= DMA_BUF_SYNC_READ;
43
+ }
44
+ if (write) {
45
+ flags |= DMA_BUF_SYNC_WRITE;
46
+ }
47
+
48
+ dmabufIoctlSync({ dmabufFd, flags });
49
+ };
50
+
51
+ const dmabufIoctlSyncEnd = ({ dmabufFd, read, write }/*: { dmabufFd: number, read: boolean; write: boolean }*/) => {
52
+
53
+ let flags = DMA_BUF_SYNC_END;
54
+ if (read) {
55
+ flags |= DMA_BUF_SYNC_READ;
56
+ }
57
+ if (write) {
58
+ flags |= DMA_BUF_SYNC_WRITE;
59
+ }
60
+
61
+ dmabufIoctlSync({ dmabufFd, flags });
62
+ };
63
+
64
+ export {
65
+ dmabufIoctlSyncStart,
66
+ dmabufIoctlSyncEnd
67
+ };
@@ -0,0 +1,23 @@
1
+ import type { TMemoryMappedBuffer } from "@k13engineering/po6-mmap";
2
+ import { type TMemoryProtectionFlags } from "@k13engineering/po6-mmap/dist/lib/convenience-api.js";
3
+ type TDmabufMappingPolicy = "forbidden" | "required" | "optional";
4
+ type TDmabufMappingAccess = {
5
+ read: TDmabufMappingPolicy;
6
+ write: TDmabufMappingPolicy;
7
+ };
8
+ type TDmabufMapping = Uint8Array & {
9
+ mappingId: number;
10
+ release: () => void;
11
+ };
12
+ declare const createMappingHelper: ({ mapAsserted }: {
13
+ mapAsserted: (args: {
14
+ memoryProtectionFlags: TMemoryProtectionFlags;
15
+ }) => TMemoryMappedBuffer;
16
+ }) => {
17
+ map: ({ access }: {
18
+ access: TDmabufMappingAccess;
19
+ }) => TDmabufMapping;
20
+ close: () => void;
21
+ };
22
+ export { createMappingHelper, };
23
+ export type { TDmabufMapping, TDmabufMappingAccess, TDmabufMappingPolicy };
@@ -0,0 +1,141 @@
1
+ /*import type { TMemoryMappedBuffer } from "@k13engineering/po6-mmap";*/
2
+ import { /*type TMemoryProtectionFlags */} from "@k13engineering/po6-mmap/dist/lib/convenience-api.js";
3
+ import { createDefaultGarbageCollectedWithoutReleaseError, createGarbageCollectionGuard } from "./snippets/gc-guard.js";
4
+ import { createCachedMapper } from "./cached-mapper.js";
5
+
6
+ /*type TDmabufMappingPolicy = "forbidden" | "required" | "optional";*/
7
+
8
+ /*type TDmabufMappingAccess = {
9
+ read: TDmabufMappingPolicy;
10
+ write: TDmabufMappingPolicy;
11
+ };*/
12
+
13
+ /*type TDmabufMapping = Uint8Array & {
14
+ mappingId: number;
15
+ release: () => void;
16
+ };*/
17
+
18
+ /*type TDmabufMappingInfo = {
19
+ mappingId: number;
20
+ };*/
21
+
22
+ const createMappingHelper = ({
23
+ mapAsserted
24
+ }/*: {
25
+ mapAsserted: (args: { memoryProtectionFlags: TMemoryProtectionFlags }) => TMemoryMappedBuffer;
26
+ }*/) => {
27
+
28
+ const readOnlyCachedMapper = createCachedMapper({
29
+ map: () => {
30
+ return mapAsserted({
31
+ memoryProtectionFlags: {
32
+ PROT_READ: true,
33
+ PROT_WRITE: false,
34
+ PROT_EXEC: false
35
+ },
36
+ });
37
+ },
38
+ });
39
+
40
+ const writeOnlyCachedMapper = createCachedMapper({
41
+ map: () => {
42
+ return mapAsserted({
43
+ memoryProtectionFlags: {
44
+ PROT_READ: false,
45
+ PROT_WRITE: true,
46
+ PROT_EXEC: false
47
+ },
48
+ });
49
+ },
50
+ });
51
+
52
+ const readWriteCachedMapper = createCachedMapper({
53
+ map: () => {
54
+ return mapAsserted({
55
+ memoryProtectionFlags: {
56
+ PROT_READ: true,
57
+ PROT_WRITE: true,
58
+ PROT_EXEC: false
59
+ },
60
+ });
61
+ },
62
+ });
63
+
64
+ const dmabufMappingGarbageCollectionGuard = createGarbageCollectionGuard({
65
+ createError: ({ info }/*: { info: TDmabufMappingInfo }*/) => {
66
+ return createDefaultGarbageCollectedWithoutReleaseError({
67
+ info: `dma buffer mapping with mappingId=${info.mappingId}`,
68
+ name: "DmabufMappingGarbageCollectedWithoutReleaseError",
69
+ releaseFunctionName: "release",
70
+ resourcesName: "dma buffer mappings",
71
+ });
72
+ },
73
+ });
74
+
75
+ let mappingIdCounter = 0;
76
+
77
+ // eslint-disable-next-line max-statements,complexity
78
+ const map = ({ access }/*: { access: TDmabufMappingAccess }*/)/*: TDmabufMapping*/ => {
79
+
80
+ const mappingId = mappingIdCounter;
81
+ mappingIdCounter += 1;
82
+ // naive approach for now
83
+
84
+ let mapperToUse/*: typeof readOnlyCachedMapper*/;
85
+
86
+ if (access.read === "forbidden" && access.write === "forbidden") {
87
+ throw Error(`invalid mapping access: read and write cannot both be forbidden`);
88
+ }
89
+
90
+ if (access.read === "required" && access.write === "required") {
91
+ mapperToUse = readWriteCachedMapper;
92
+ } else if (access.write === "required" || access.read === "forbidden") {
93
+ mapperToUse = writeOnlyCachedMapper;
94
+ } else {
95
+ mapperToUse = readOnlyCachedMapper;
96
+ }
97
+
98
+ const mappedBuffer = mapperToUse.maybeMap();
99
+
100
+ const mappingInfo/*: TDmabufMappingInfo*/ = {
101
+ mappingId
102
+ };
103
+
104
+ const { release } = dmabufMappingGarbageCollectionGuard.protect({
105
+ release: () => {
106
+ mappedBuffer.release();
107
+ },
108
+
109
+ info: mappingInfo
110
+ });
111
+
112
+ const bufferView = new Uint8Array(mappedBuffer.buffer, mappedBuffer.byteOffset, mappedBuffer.byteLength);
113
+ const mapping = bufferView /*as TDmabufMapping*/;
114
+
115
+ mapping.mappingId = mappingId;
116
+ mapping.release = release;
117
+
118
+ return mapping;
119
+ };
120
+
121
+ const close = () => {
122
+ readOnlyCachedMapper.close();
123
+ writeOnlyCachedMapper.close();
124
+ readWriteCachedMapper.close();
125
+ };
126
+
127
+ return {
128
+ map,
129
+ close
130
+ };
131
+ };
132
+
133
+ export {
134
+ createMappingHelper,
135
+ };
136
+
137
+ /*export type {
138
+ TDmabufMapping,
139
+ TDmabufMappingAccess,
140
+ TDmabufMappingPolicy
141
+ };*/
@@ -0,0 +1,20 @@
1
+ import type { TDmabufHandle } from "./dmabuf-handle.js";
2
+ type TTransactionFunctionArgs = {
3
+ use: (args: {
4
+ handle: TDmabufHandle;
5
+ access: TDmabufUseAccess;
6
+ }) => Uint8Array;
7
+ };
8
+ type TTransactionFunction<T> = (args: TTransactionFunctionArgs) => T;
9
+ type TDmabufUseAccess = {
10
+ read: "required";
11
+ write: "required";
12
+ } | {
13
+ read: "required";
14
+ write: "crash-process-on-write";
15
+ } | {
16
+ read: "crash-process-on-read";
17
+ write: "required";
18
+ };
19
+ declare const dmabufTransaction: <T>(fn: TTransactionFunction<T>) => T;
20
+ export { dmabufTransaction };
@@ -0,0 +1,114 @@
1
+ /*import type { TDmabufMapping } from "./dmabuf-mapping.ts";*/
2
+ /*import type { TDmabufHandle, TDmabufSync } from "./dmabuf-handle.ts";*/
3
+
4
+ /*type TTransactionFunctionArgs = {
5
+ use: (args: {
6
+ handle: TDmabufHandle;
7
+ access: TDmabufUseAccess
8
+ }) => Uint8Array;
9
+ };*/
10
+
11
+ /*type TTransactionFunction<T> = (args: TTransactionFunctionArgs) => T;*/
12
+
13
+ /*type TDmabufUseAccess = {
14
+ read: "required",
15
+ write: "required"
16
+ } | {
17
+ read: "required",
18
+ write: "crash-process-on-write"
19
+ } | {
20
+ read: "crash-process-on-read",
21
+ write: "required"
22
+ };*/
23
+
24
+ // type TDmabufReadUseAccess = TDmabufUseAccess["read"];
25
+ // type TDmabufWriteUseAccess = TDmabufUseAccess["write"];
26
+
27
+ /*type TUseInternal = {
28
+ handle: TDmabufHandle;
29
+ sync: TDmabufSync;
30
+ mapping: TDmabufMapping;
31
+ };*/
32
+
33
+ let transactionEntered = false;
34
+
35
+ const dmabufTransaction = /*<T>*/(fn/*: TTransactionFunction<T>*/) => {
36
+
37
+ if (transactionEntered) {
38
+ throw Error("nested transactions are not allowed");
39
+ }
40
+
41
+ let internalUses/*: TUseInternal[]*/ = [];
42
+
43
+ const assertHandleNotAlreadyUsed = ({ handle }/*: { handle: TDmabufHandle }*/) => {
44
+ const existingUses = internalUses.filter((internalUse) => {
45
+ return internalUse.handle === handle;
46
+ });
47
+
48
+ if (existingUses.length > 0) {
49
+ throw Error(`dmabuf handle (inode ${handle.info().inode}) is already used in this transaction`);
50
+ }
51
+ };
52
+
53
+ const use = ({
54
+ handle,
55
+ access,
56
+ }/*: {
57
+ handle: TDmabufHandle;
58
+ access: TDmabufUseAccess
59
+ }*/) => {
60
+ assertHandleNotAlreadyUsed({ handle });
61
+
62
+ const mapping = handle.map({
63
+ iKnowWhatImDoing: true,
64
+ access: {
65
+ read: access.read === "required" ? "required" : "forbidden",
66
+ write: access.write === "required" ? "required" : "forbidden"
67
+ }
68
+ });
69
+
70
+ // @ts-expect-error 2345 - TypeScript does not get this right
71
+ const sync = handle.sync({
72
+ iKnowWhatImDoing: true,
73
+ read: access.read === "required",
74
+ write: access.write === "required"
75
+ });
76
+
77
+ const internalHandle/*: TUseInternal*/ = {
78
+ handle,
79
+ sync,
80
+ mapping
81
+ };
82
+
83
+ internalUses = [
84
+ ...internalUses,
85
+ internalHandle
86
+ ];
87
+
88
+ const buffer = new Uint8Array(mapping.buffer, mapping.byteOffset, mapping.byteLength);
89
+ return buffer;
90
+ };
91
+
92
+ transactionEntered = true;
93
+
94
+ let result/*: T*/;
95
+
96
+ try {
97
+ result = fn({
98
+ use
99
+ });
100
+ } finally {
101
+ transactionEntered = false;
102
+ }
103
+
104
+ internalUses.forEach(({ sync, mapping }) => {
105
+ sync.end();
106
+ mapping.release();
107
+ });
108
+
109
+ return result;
110
+ };
111
+
112
+ export {
113
+ dmabufTransaction
114
+ };
@@ -0,0 +1,6 @@
1
+ import { dmabufTransaction } from "./dmabuf-transaction.js";
2
+ import type { TDmabufHandle, TImportAndDupDmabufFunc } from "./dmabuf-handle.js";
3
+ import type { TDmabufMapping } from "./dmabuf-mapping.js";
4
+ declare const importAndDupDmabuf: TImportAndDupDmabufFunc;
5
+ export { dmabufTransaction, importAndDupDmabuf };
6
+ export type { TDmabufHandle, TDmabufMapping, };
package/dist/lib/index.js CHANGED
@@ -1 +1,24 @@
1
1
 
2
+ import { dmabufTransaction } from "./dmabuf-transaction.js";
3
+ import { createHandleImporter } from "./dmabuf-handle.js";
4
+ /*import type { TDmabufHandle, TImportAndDupDmabufFunc } from "./dmabuf-handle.ts";*/
5
+ /*import type { TDmabufMapping } from "./dmabuf-mapping.ts";*/
6
+ import { createLinuxHostInterface } from "./linux-interface-impl.js";
7
+
8
+ const linuxInterface = createLinuxHostInterface();
9
+
10
+ const handleImporter = createHandleImporter({
11
+ linuxInterface
12
+ });
13
+
14
+ const importAndDupDmabuf = handleImporter.importAndDupDmabuf /*as TImportAndDupDmabufFunc*/;
15
+
16
+ export {
17
+ dmabufTransaction,
18
+ importAndDupDmabuf
19
+ };
20
+
21
+ /*export type {
22
+ TDmabufHandle,
23
+ TDmabufMapping,
24
+ };*/
@@ -0,0 +1,3 @@
1
+ import type { TLinuxDmabufInterface } from "./linux-interface.js";
2
+ declare const createLinuxHostInterface: () => TLinuxDmabufInterface;
3
+ export { createLinuxHostInterface };
@@ -0,0 +1,44 @@
1
+ /*import type { TLinuxDmabufInterface } from "./linux-interface.ts";*/
2
+ import { mmapFd } from "@k13engineering/po6-mmap";
3
+ import { dmabufIoctlSyncStart, dmabufIoctlSyncEnd } from "./dmabuf-ioctl.js";
4
+ import { syscall, syscallNumbers } from "syscall-napi";
5
+ import nodeFs from "node:fs";
6
+
7
+ const dup = ({ fd }/*: { fd: number }*/)/*: number*/ => {
8
+ const { errno, ret: newFd } = syscall({
9
+ syscallNumber: syscallNumbers.dup,
10
+ args: [
11
+ BigInt(fd)
12
+ ]
13
+ });
14
+
15
+ if (errno !== undefined) {
16
+ throw Error(`dup failed with errno ${errno}`);
17
+ }
18
+
19
+ return Number(newFd);
20
+ };
21
+
22
+ const fstat = ({ fd }/*: { fd: number }*/) => {
23
+ const st = nodeFs.fstatSync(fd);
24
+ return { inode: st.ino, size: st.size };
25
+ };
26
+
27
+ const close = ({ fd }/*: { fd: number }*/) => {
28
+ nodeFs.closeSync(fd);
29
+ };
30
+
31
+ const createLinuxHostInterface = ()/*: TLinuxDmabufInterface*/ => {
32
+ return {
33
+ mmapFd,
34
+ dmabufIoctlSyncEnd,
35
+ dmabufIoctlSyncStart,
36
+ dup,
37
+ fstat,
38
+ close
39
+ };
40
+ };
41
+
42
+ export {
43
+ createLinuxHostInterface
44
+ };
@@ -0,0 +1,23 @@
1
+ import type { dmabufIoctlSyncEnd, dmabufIoctlSyncStart } from "./dmabuf-ioctl.js";
2
+ import type { mmapFd } from "@k13engineering/po6-mmap";
3
+ type TDmabufIoctlSyncEndFunc = typeof dmabufIoctlSyncEnd;
4
+ type TDmabufIoctlSyncStartFunc = typeof dmabufIoctlSyncStart;
5
+ type TMmapFdFunc = typeof mmapFd;
6
+ type TLinuxDmabufInterface = {
7
+ mmapFd: TMmapFdFunc;
8
+ dmabufIoctlSyncEnd: TDmabufIoctlSyncEndFunc;
9
+ dmabufIoctlSyncStart: TDmabufIoctlSyncStartFunc;
10
+ dup: (args: {
11
+ fd: number;
12
+ }) => number;
13
+ fstat: (args: {
14
+ fd: number;
15
+ }) => {
16
+ inode: number;
17
+ size: number;
18
+ };
19
+ close: (args: {
20
+ fd: number;
21
+ }) => void;
22
+ };
23
+ export type { TLinuxDmabufInterface };
@@ -0,0 +1,24 @@
1
+ /* c8 ignore start */
2
+ /*import type {
3
+ dmabufIoctlSyncEnd,
4
+ dmabufIoctlSyncStart,
5
+ } from "./dmabuf-ioctl.ts";*/
6
+ /*import type { mmapFd } from "@k13engineering/po6-mmap";*/
7
+
8
+ /*type TDmabufIoctlSyncEndFunc = typeof dmabufIoctlSyncEnd;*/
9
+ /*type TDmabufIoctlSyncStartFunc = typeof dmabufIoctlSyncStart;*/
10
+ /*type TMmapFdFunc = typeof mmapFd;*/
11
+
12
+ /*type TLinuxDmabufInterface = {
13
+ mmapFd: TMmapFdFunc;
14
+ dmabufIoctlSyncEnd: TDmabufIoctlSyncEndFunc;
15
+ dmabufIoctlSyncStart: TDmabufIoctlSyncStartFunc;
16
+ dup: (args: { fd: number }) => number;
17
+ fstat: (args: { fd: number }) => { inode: number; size: number };
18
+ close: (args: { fd: number }) => void;
19
+ };*/
20
+
21
+ /*export type {
22
+ TLinuxDmabufInterface
23
+ };*/
24
+ /* c8 ignore end */
@@ -0,0 +1,20 @@
1
+ type TInstance = {
2
+ release: () => void;
3
+ };
4
+ declare const createGarbageCollectionGuard: <T>({ createError, }: {
5
+ createError: (args: {
6
+ info: T;
7
+ }) => Error;
8
+ }) => {
9
+ protect: ({ release: providedRelease, info }: {
10
+ release: () => void;
11
+ info: T;
12
+ }) => TInstance;
13
+ };
14
+ declare const createDefaultGarbageCollectedWithoutReleaseError: ({ name, info, releaseFunctionName, resourcesName, }: {
15
+ name: string;
16
+ info: string;
17
+ releaseFunctionName: string;
18
+ resourcesName: string;
19
+ }) => any;
20
+ export { createGarbageCollectionGuard, createDefaultGarbageCollectedWithoutReleaseError };
@@ -0,0 +1,65 @@
1
+ /*type TInstance = {
2
+ release: () => void;
3
+ };*/
4
+
5
+ const createGarbageCollectionGuard = /*<T>*/({
6
+ createError,
7
+ }/*: {
8
+ createError: (args: { info: T }) => Error,
9
+ }*/) => {
10
+
11
+ const instanceFinalizationRegistry = new FinalizationRegistry((info/*: T*/) => {
12
+ // this callback is called when a instance is garbage collected without release being called
13
+ throw createError({ info });
14
+ });
15
+
16
+ const protect = ({
17
+ release: providedRelease,
18
+ info
19
+ }/*: { release: () => void; info: T }*/)/*: TInstance*/ => {
20
+ const release = () => {
21
+ providedRelease();
22
+ instanceFinalizationRegistry.unregister(release);
23
+ };
24
+
25
+ instanceFinalizationRegistry.register(release, info, release);
26
+
27
+ return {
28
+ release
29
+ };
30
+ };
31
+
32
+ return {
33
+ protect
34
+ };
35
+ };
36
+
37
+ const createDefaultGarbageCollectedWithoutReleaseError = ({
38
+ name,
39
+ info,
40
+ releaseFunctionName,
41
+ resourcesName,
42
+ }/*: {
43
+ name: string,
44
+ info: string,
45
+ releaseFunctionName: string,
46
+ resourcesName: string,
47
+ }*/) => {
48
+
49
+ let message = `${info} was garbage collected without calling release().`;
50
+ message += ` This would cause a memory leak -`;
51
+ message += ` therefore this raises an uncaught exception.`;
52
+ message += ` Please make sure to call ${releaseFunctionName}() on all ${resourcesName} when you are done with them.`;
53
+ message += ` Underlying resources should have still be cleaned up in case you are catching uncaught exceptions -`;
54
+ message += ` however, do not rely on this behavior (specifically for production code).`;
55
+
56
+ const error = Error(message);
57
+ error.name = name;
58
+
59
+ return error;
60
+ };
61
+
62
+ export {
63
+ createGarbageCollectionGuard,
64
+ createDefaultGarbageCollectedWithoutReleaseError
65
+ };
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.0.2",
2
+ "version": "0.0.3",
3
3
  "name": "@k13engineering/linux-dmabuf",
4
4
  "type": "module",
5
5
  "description": "DMA buffer utilities for Node.js",
@@ -9,18 +9,24 @@
9
9
  "main": "dist/lib/index.js",
10
10
  "scripts": {
11
11
  "build": "rm -rf dist/ && deno-node-build --root . --out dist/ --entry lib/index.ts",
12
- "test": "c8 --reporter lcov --reporter html --reporter text --all --src lib/ node test/index.ts",
12
+ "test": "c8 --reporter lcov --reporter html --reporter text --all --src lib/ --exclude lib/snippets --exclude test/ mocha test/**/*.ts",
13
13
  "lint": "eslint ."
14
14
  },
15
15
  "devDependencies": {
16
16
  "@eslint/js": "^9.17.0",
17
17
  "@k13engineering/releasetool": "^0.0.5",
18
+ "@types/mocha": "^10.0.10",
18
19
  "@types/node": "^22.10.5",
19
20
  "c8": "^10.1.3",
20
21
  "deno-node": "^0.0.12",
22
+ "mocha": "^11.7.5",
21
23
  "typescript-eslint": "^8.19.0"
22
24
  },
23
- "dependencies": {},
25
+ "dependencies": {
26
+ "@k13engineering/po6-ioctl": "^0.0.2",
27
+ "@k13engineering/po6-mmap": "^0.0.6",
28
+ "syscall-napi": "^0.1.5"
29
+ },
24
30
  "publishConfig": {
25
31
  "access": "public"
26
32
  },