pinokiod 8.0.45 → 8.0.47
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.
- package/kernel/gpu/amd_gfx_targets.json +3 -1
- package/kernel/gpu/amd_pci.js +184 -0
- package/kernel/gpu/amd_pci_targets.json +3243 -0
- package/kernel/sysinfo.js +6 -1
- package/kernel/vault/constants.js +9 -2
- package/kernel/vault/hash_worker.js +13 -1
- package/kernel/vault/index.js +83 -23
- package/kernel/vault/sweeper.js +15 -23
- package/package.json +2 -1
- package/script/update-amd-gfx-targets.js +289 -13
- package/server/lib/app_log_report.js +45 -3
- package/server/lib/log_redaction.js +26 -14
- package/server/public/nav.js +1 -1
- package/server/public/style.css +0 -1
- package/server/public/vault.css +6 -1
- package/server/public/vault.js +62 -32
- package/server/views/partials/vault_workspace.ejs +1 -0
- package/server/views/vault.ejs +2 -0
- package/test/amd-gfx-generator.test.js +75 -0
- package/test/amd-gpu-target.test.js +108 -10
- package/test/amd-pci-target.test.js +159 -0
- package/test/app-log-report.test.js +67 -0
- package/test/header-collapse.test.js +10 -5
- package/test/sysinfo-gpu-driver.test.js +4 -0
- package/test/vault-engine.test.js +60 -1
- package/test/vault-sweep.test.js +50 -31
- package/test/vault-ui.test.js +145 -14
package/kernel/sysinfo.js
CHANGED
|
@@ -3,6 +3,7 @@ const fs = require('fs')
|
|
|
3
3
|
const path = require('path')
|
|
4
4
|
const nvidia = require("./gpu/nvidia")
|
|
5
5
|
const amd = require("./gpu/amd")
|
|
6
|
+
const amd_pci = require("./gpu/amd_pci")
|
|
6
7
|
class Sysinfo {
|
|
7
8
|
async init(kernel) {
|
|
8
9
|
this.kernel = kernel
|
|
@@ -139,7 +140,11 @@ class Sysinfo {
|
|
|
139
140
|
if (detected.is_nvidia) {
|
|
140
141
|
return await nvidia.resolve_cuda_sm_target(detected.primaryController)
|
|
141
142
|
} else if (detected.is_amd) {
|
|
142
|
-
|
|
143
|
+
let target = await amd.resolve_gpu_target(
|
|
144
|
+
detected.gpu_model,
|
|
145
|
+
detected.cpu_brand || (() => this.cpu_brand())
|
|
146
|
+
)
|
|
147
|
+
return target || await amd_pci.resolve_gpu_target(detected.primaryController)
|
|
143
148
|
} else {
|
|
144
149
|
return null
|
|
145
150
|
}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
const CANDIDATE_SIZE_BASE = process.platform === "win32" ? 1024 : 1000
|
|
2
|
+
const CANDIDATE_SIZE_OPTIONS = [10, 50, 100, 500]
|
|
3
|
+
.map((value) => value * CANDIDATE_SIZE_BASE ** 2)
|
|
4
|
+
.concat(CANDIDATE_SIZE_BASE ** 3)
|
|
5
|
+
|
|
1
6
|
module.exports = {
|
|
2
|
-
SIZE_THRESHOLD:
|
|
7
|
+
SIZE_THRESHOLD: CANDIDATE_SIZE_OPTIONS[2],
|
|
8
|
+
CANDIDATE_SIZE_OPTIONS,
|
|
3
9
|
TMP_SUFFIX: ".pinokio-dedup-tmp",
|
|
4
10
|
SHA256_RE: /^[0-9a-f]{64}$/,
|
|
5
11
|
ENTRY_BATCH_SIZE: 256,
|
|
6
12
|
DIR_CONCURRENCY: 8,
|
|
7
13
|
STAT_CONCURRENCY: 32,
|
|
8
|
-
HASH_QUEUE_LIMIT: 256
|
|
14
|
+
HASH_QUEUE_LIMIT: 256,
|
|
15
|
+
HASH_INACTIVITY_MS: 120 * 1000
|
|
9
16
|
}
|
|
@@ -6,20 +6,32 @@ const fs = require('fs')
|
|
|
6
6
|
// events. This changes only the read granularity; every byte still feeds the
|
|
7
7
|
// same sha256 digest, one file at a time.
|
|
8
8
|
const HASH_READ_SIZE = 1024 * 1024
|
|
9
|
+
const HASH_PROGRESS_INTERVAL_MS = 1000
|
|
9
10
|
|
|
10
11
|
parentPort.on('message', ({ id, filePath }) => {
|
|
11
12
|
const hash = crypto.createHash('sha256')
|
|
12
13
|
let size = 0
|
|
14
|
+
let lastProgressAt = 0
|
|
15
|
+
let complete = false
|
|
13
16
|
const flags = fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0)
|
|
14
17
|
const stream = fs.createReadStream(filePath, { flags, highWaterMark: HASH_READ_SIZE })
|
|
15
18
|
stream.on('data', (chunk) => {
|
|
16
19
|
size += chunk.length
|
|
17
20
|
hash.update(chunk)
|
|
21
|
+
const now = Date.now()
|
|
22
|
+
if (now - lastProgressAt >= HASH_PROGRESS_INTERVAL_MS) {
|
|
23
|
+
lastProgressAt = now
|
|
24
|
+
parentPort.postMessage({ id, bytes_read: size })
|
|
25
|
+
}
|
|
18
26
|
})
|
|
19
27
|
stream.on('error', (error) => {
|
|
20
|
-
|
|
28
|
+
if (complete) return
|
|
29
|
+
complete = true
|
|
30
|
+
parentPort.postMessage({ id, error: error.message, code: error.code })
|
|
21
31
|
})
|
|
22
32
|
stream.on('end', () => {
|
|
33
|
+
if (complete) return
|
|
34
|
+
complete = true
|
|
23
35
|
parentPort.postMessage({ id, hash: hash.digest('hex'), size })
|
|
24
36
|
})
|
|
25
37
|
})
|
package/kernel/vault/index.js
CHANGED
|
@@ -6,7 +6,10 @@ const Registry = require('./registry')
|
|
|
6
6
|
const Sweeper = require('./sweeper')
|
|
7
7
|
const { walkBatches, statMany } = require('./walker')
|
|
8
8
|
const { fileSnapshot, sameSnapshot, sameContentState } = require('./snapshot')
|
|
9
|
-
const {
|
|
9
|
+
const {
|
|
10
|
+
SIZE_THRESHOLD, CANDIDATE_SIZE_OPTIONS, TMP_SUFFIX, SHA256_RE,
|
|
11
|
+
DIR_CONCURRENCY, STAT_CONCURRENCY, HASH_INACTIVITY_MS
|
|
12
|
+
} = require('./constants')
|
|
10
13
|
|
|
11
14
|
// Shared model store engine (spec/requirements/shared-model-store.md).
|
|
12
15
|
// Store + registry + volume probe + hashing + adopt/convert/verify, plus the
|
|
@@ -81,6 +84,7 @@ class Vault {
|
|
|
81
84
|
this.workerSeq = 0
|
|
82
85
|
this.workerIdleTimer = null
|
|
83
86
|
this.workerIdleMs = 750
|
|
87
|
+
this.hashInactivityMs = HASH_INACTIVITY_MS
|
|
84
88
|
this.statConcurrency = STAT_CONCURRENCY
|
|
85
89
|
this.dirConcurrency = DIR_CONCURRENCY
|
|
86
90
|
this.sizeThreshold = SIZE_THRESHOLD
|
|
@@ -167,9 +171,10 @@ class Vault {
|
|
|
167
171
|
if (this.fileActionProgress === progress) this.fileActionProgress = null
|
|
168
172
|
}
|
|
169
173
|
}
|
|
170
|
-
startScan(scopeId = null) {
|
|
174
|
+
startScan(scopeId = null, sizeThreshold = this.sizeThreshold) {
|
|
171
175
|
if (!this.enabled || !this.sweeper) return { started: false, disabled: true }
|
|
172
176
|
if (this.scanPromise) return { started: false, already_running: true }
|
|
177
|
+
this.sizeThreshold = sizeThreshold
|
|
173
178
|
this.scanError = null
|
|
174
179
|
this.scanScopeId = scopeId
|
|
175
180
|
this.scanPromise = this.runExclusive(() => this.sweeper.scan(scopeId))
|
|
@@ -200,11 +205,19 @@ class Vault {
|
|
|
200
205
|
}
|
|
201
206
|
}
|
|
202
207
|
}
|
|
203
|
-
case "scan":
|
|
208
|
+
case "scan": {
|
|
204
209
|
if (payload.scope_id && !this.scanSource(payload.scope_id)) {
|
|
205
210
|
return { error: "That scan location is no longer available." }
|
|
206
211
|
}
|
|
207
|
-
|
|
212
|
+
let sizeThreshold = this.sizeThreshold
|
|
213
|
+
if (payload.candidate_size != null) {
|
|
214
|
+
if (!CANDIDATE_SIZE_OPTIONS.includes(payload.candidate_size)) {
|
|
215
|
+
return { error: "Choose a valid minimum file size." }
|
|
216
|
+
}
|
|
217
|
+
sizeThreshold = payload.candidate_size
|
|
218
|
+
}
|
|
219
|
+
return this.startScan(payload.scope_id || null, sizeThreshold)
|
|
220
|
+
}
|
|
208
221
|
case "deduplicate":
|
|
209
222
|
if (typeof payload.path === "string" && payload.path) {
|
|
210
223
|
return this.runMutation(() => this.runFileAction({
|
|
@@ -675,7 +688,21 @@ class Vault {
|
|
|
675
688
|
this.volumeModes.set(dev, mode)
|
|
676
689
|
return mode
|
|
677
690
|
}
|
|
678
|
-
|
|
691
|
+
failHashWorker(worker, error, terminate = false) {
|
|
692
|
+
if (this.worker === worker) {
|
|
693
|
+
if (this.workerIdleTimer) clearTimeout(this.workerIdleTimer)
|
|
694
|
+
this.workerIdleTimer = null
|
|
695
|
+
this.worker = null
|
|
696
|
+
}
|
|
697
|
+
for (const [id, job] of [...this.workerJobs]) {
|
|
698
|
+
if (job.worker !== worker) continue
|
|
699
|
+
clearTimeout(job.inactivityTimer)
|
|
700
|
+
this.workerJobs.delete(id)
|
|
701
|
+
job.reject(error)
|
|
702
|
+
}
|
|
703
|
+
if (terminate) worker.terminate().catch(() => {})
|
|
704
|
+
}
|
|
705
|
+
async hashFile(filePath, options = {}) {
|
|
679
706
|
if (this.workerIdleTimer) {
|
|
680
707
|
clearTimeout(this.workerIdleTimer)
|
|
681
708
|
this.workerIdleTimer = null
|
|
@@ -684,12 +711,24 @@ class Vault {
|
|
|
684
711
|
const worker = new Worker(path.resolve(__dirname, "hash_worker.js"))
|
|
685
712
|
this.worker = worker
|
|
686
713
|
worker.unref()
|
|
687
|
-
worker.on("message", ({ id, hash, size, error }) => {
|
|
714
|
+
worker.on("message", ({ id, hash, size, bytes_read: bytesRead, error, code }) => {
|
|
688
715
|
const job = this.workerJobs.get(id)
|
|
689
|
-
if (!job) return
|
|
716
|
+
if (!job || job.worker !== worker) return
|
|
717
|
+
if (Number.isFinite(bytesRead)) {
|
|
718
|
+
job.resetInactivity()
|
|
719
|
+
job.reportProgress(bytesRead)
|
|
720
|
+
return
|
|
721
|
+
}
|
|
722
|
+
clearTimeout(job.inactivityTimer)
|
|
690
723
|
this.workerJobs.delete(id)
|
|
691
|
-
if (error)
|
|
692
|
-
|
|
724
|
+
if (error) {
|
|
725
|
+
const failure = new Error(error)
|
|
726
|
+
if (code) failure.code = code
|
|
727
|
+
job.reject(failure)
|
|
728
|
+
} else {
|
|
729
|
+
job.reportProgress(size)
|
|
730
|
+
job.resolve({ hash, size })
|
|
731
|
+
}
|
|
693
732
|
// Keep the worker warm across the scan queue. Starting one worker per
|
|
694
733
|
// file costs ~30ms and adds up quickly on a first scan.
|
|
695
734
|
if (this.workerJobs.size === 0 && this.worker === worker) {
|
|
@@ -705,27 +744,46 @@ class Vault {
|
|
|
705
744
|
})
|
|
706
745
|
worker.on("error", (error) => {
|
|
707
746
|
if (this.worker !== worker) return
|
|
708
|
-
|
|
709
|
-
this.workerIdleTimer = null
|
|
710
|
-
for (const job of this.workerJobs.values()) job.reject(error)
|
|
711
|
-
this.workerJobs.clear()
|
|
712
|
-
this.worker = null
|
|
747
|
+
this.failHashWorker(worker, error)
|
|
713
748
|
})
|
|
714
749
|
worker.on("exit", (code) => {
|
|
715
750
|
if (this.worker !== worker) return
|
|
716
|
-
|
|
717
|
-
this.workerIdleTimer = null
|
|
718
|
-
for (const job of this.workerJobs.values()) {
|
|
719
|
-
job.reject(new Error(`hash worker exited with code ${code}`))
|
|
720
|
-
}
|
|
721
|
-
this.workerJobs.clear()
|
|
722
|
-
this.worker = null
|
|
751
|
+
this.failHashWorker(worker, new Error(`hash worker exited with code ${code}`))
|
|
723
752
|
})
|
|
724
753
|
}
|
|
754
|
+
const worker = this.worker
|
|
725
755
|
const id = ++this.workerSeq
|
|
726
756
|
return new Promise((resolve, reject) => {
|
|
727
|
-
|
|
728
|
-
|
|
757
|
+
const onProgress = typeof options.onProgress === "function" ? options.onProgress : null
|
|
758
|
+
const job = {
|
|
759
|
+
worker,
|
|
760
|
+
resolve,
|
|
761
|
+
reject,
|
|
762
|
+
reportProgress: (bytes) => {
|
|
763
|
+
if (!onProgress) return
|
|
764
|
+
// Progress is observational and must never fail a content hash.
|
|
765
|
+
try { onProgress(bytes) } catch (_) {}
|
|
766
|
+
},
|
|
767
|
+
inactivityTimer: null,
|
|
768
|
+
resetInactivity: null
|
|
769
|
+
}
|
|
770
|
+
job.resetInactivity = () => {
|
|
771
|
+
clearTimeout(job.inactivityTimer)
|
|
772
|
+
const inactivityMs = Math.max(1, Number(this.hashInactivityMs) || HASH_INACTIVITY_MS)
|
|
773
|
+
job.inactivityTimer = setTimeout(() => {
|
|
774
|
+
const failure = new Error(`Timed out while reading ${path.basename(filePath)}`)
|
|
775
|
+
failure.code = "ETIMEDOUT"
|
|
776
|
+
this.failHashWorker(worker, failure, true)
|
|
777
|
+
}, inactivityMs)
|
|
778
|
+
if (job.inactivityTimer.unref) job.inactivityTimer.unref()
|
|
779
|
+
}
|
|
780
|
+
this.workerJobs.set(id, job)
|
|
781
|
+
job.resetInactivity()
|
|
782
|
+
try {
|
|
783
|
+
worker.postMessage({ id, filePath })
|
|
784
|
+
} catch (error) {
|
|
785
|
+
this.failHashWorker(worker, error, true)
|
|
786
|
+
}
|
|
729
787
|
})
|
|
730
788
|
}
|
|
731
789
|
async refreshLinkSnapshots(hash, dev, ino) {
|
|
@@ -1798,6 +1856,8 @@ class Vault {
|
|
|
1798
1856
|
: sweeper.state
|
|
1799
1857
|
return Object.assign({}, state, {
|
|
1800
1858
|
current_file: sweeper.currentHash ? path.basename(sweeper.currentHash.path) : null,
|
|
1859
|
+
current_file_bytes: sweeper.currentHash ? sweeper.currentHash.bytes : null,
|
|
1860
|
+
current_file_size: sweeper.currentHash ? sweeper.currentHash.size : null,
|
|
1801
1861
|
pending,
|
|
1802
1862
|
error: this.scanError
|
|
1803
1863
|
})
|
package/kernel/vault/sweeper.js
CHANGED
|
@@ -54,12 +54,11 @@ class Sweeper {
|
|
|
54
54
|
idleState() {
|
|
55
55
|
return {
|
|
56
56
|
active: false, phase: "idle", dirs: 0, files: 0, bytes_total: 0,
|
|
57
|
-
counted_dirs: 0, counted_files: 0, total_files: null,
|
|
57
|
+
counted_dirs: 0, counted_files: 0, total_files: null,
|
|
58
58
|
home_bytes_total: 0, source_bytes: {}, source_files: {}, source_hash_failures: {}, scope_id: null,
|
|
59
59
|
candidates: 0, hashed: 0, hash_total: 0, hash_bytes: 0, queued: 0,
|
|
60
60
|
inode_reuses: 0, unstable_hashes: 0, hash_failures: 0,
|
|
61
61
|
inaccessible: 0, inaccessible_paths: [],
|
|
62
|
-
estimated_count_weight: 0.4, estimated_walk_weight: 0.5,
|
|
63
62
|
started: null, duration_ms: null,
|
|
64
63
|
count_duration_ms: null, walk_duration_ms: null, hash_wait_duration_ms: null,
|
|
65
64
|
hash_duration_ms: 0
|
|
@@ -84,24 +83,6 @@ class Sweeper {
|
|
|
84
83
|
if (!scopeId) this.vault.reconcileConfiguredSources()
|
|
85
84
|
const scanRoots = this.vault.scanRoots(scopeId)
|
|
86
85
|
if (!scanRoots.length) throw new Error("That scan location is no longer available.")
|
|
87
|
-
const previous = this.vault.registry.scanFor(scopeId)
|
|
88
|
-
if (previous) {
|
|
89
|
-
if (Number.isFinite(previous.files) && previous.files > 0) {
|
|
90
|
-
this.state.count_estimate_files = previous.files
|
|
91
|
-
}
|
|
92
|
-
const duration = Math.max(0, Number(previous.duration_ms) || 0)
|
|
93
|
-
const walkDuration = Math.max(0, Number(previous.walk_duration_ms) || 0)
|
|
94
|
-
const waitDuration = Math.max(0, Number(previous.hash_wait_duration_ms) || 0)
|
|
95
|
-
const countDuration = Number.isFinite(previous.count_duration_ms)
|
|
96
|
-
? Math.max(0, previous.count_duration_ms)
|
|
97
|
-
: Math.max(0, duration - walkDuration - waitDuration)
|
|
98
|
-
const finishDuration = Math.max(1, duration - countDuration - walkDuration)
|
|
99
|
-
const measuredDuration = countDuration + walkDuration + finishDuration
|
|
100
|
-
if (measuredDuration > 1) {
|
|
101
|
-
this.state.estimated_count_weight = countDuration / measuredDuration
|
|
102
|
-
this.state.estimated_walk_weight = walkDuration / measuredDuration
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
86
|
const countStarted = Date.now()
|
|
106
87
|
for (const source of scanRoots) await this.countFiles(source.root)
|
|
107
88
|
this.state.count_duration_ms = Date.now() - countStarted
|
|
@@ -411,9 +392,20 @@ class Sweeper {
|
|
|
411
392
|
async _hashJob(job) {
|
|
412
393
|
const primary = job.names[0]
|
|
413
394
|
const started = Date.now()
|
|
414
|
-
|
|
395
|
+
const currentHash = {
|
|
396
|
+
path: primary.filePath,
|
|
397
|
+
size: Math.max(0, Number(primary.expected.size) || 0),
|
|
398
|
+
bytes: 0
|
|
399
|
+
}
|
|
400
|
+
this.currentHash = currentHash
|
|
415
401
|
try {
|
|
416
|
-
const { hash, size } = await this.vault.hashFile(primary.filePath
|
|
402
|
+
const { hash, size } = await this.vault.hashFile(primary.filePath, {
|
|
403
|
+
onProgress: (bytes) => {
|
|
404
|
+
if (this.currentHash === currentHash) {
|
|
405
|
+
currentHash.bytes = Math.min(currentHash.size, Math.max(0, Number(bytes) || 0))
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
})
|
|
417
409
|
this.state.hashed += 1
|
|
418
410
|
this.state.hash_bytes += size || 0
|
|
419
411
|
const primaryStat = await fs.promises.lstat(primary.filePath)
|
|
@@ -460,7 +452,7 @@ class Sweeper {
|
|
|
460
452
|
if (job.inodeKey && this.hashJobsByIno.get(job.inodeKey) === job) {
|
|
461
453
|
this.hashJobsByIno.delete(job.inodeKey)
|
|
462
454
|
}
|
|
463
|
-
this.currentHash = null
|
|
455
|
+
if (this.currentHash === currentHash) this.currentHash = null
|
|
464
456
|
this.state.queued = Math.max(0, this.state.queued - 1)
|
|
465
457
|
if (this.state.queued < this.hashQueueLimit) {
|
|
466
458
|
for (const resolve of this.hashCapacityWaiters.splice(0)) resolve()
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pinokiod",
|
|
3
|
-
"version": "8.0.
|
|
3
|
+
"version": "8.0.47",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"download_readme": "wget -O kernel/proto/PINOKIO.md https://raw.githubusercontent.com/pinokiocomputer/home/refs/heads/main/docs/README.md",
|
|
8
8
|
"update:amd-gfx-targets": "node script/update-amd-gfx-targets.js",
|
|
9
|
+
"check:amd-gfx-targets": "node script/update-amd-gfx-targets.js --check",
|
|
9
10
|
"start": "node script/index"
|
|
10
11
|
},
|
|
11
12
|
"author": "",
|
|
@@ -1,11 +1,65 @@
|
|
|
1
|
+
const crypto = require('node:crypto')
|
|
1
2
|
const fs = require('node:fs')
|
|
2
3
|
const https = require('node:https')
|
|
3
4
|
const path = require('node:path')
|
|
4
5
|
|
|
5
6
|
const { normalize_model } = require('../kernel/gpu/common')
|
|
6
7
|
|
|
7
|
-
const GPU_SPECS_URL = 'https://raw.githubusercontent.com/ROCm/ROCm/develop/docs/reference/gpu-arch-specs.rst'
|
|
8
8
|
const GENERATOR = 'script/update-amd-gfx-targets.js'
|
|
9
|
+
const AMD_VENDOR_ID = '1002'
|
|
10
|
+
|
|
11
|
+
// Update these revisions deliberately during release maintenance. Runtime GPU
|
|
12
|
+
// detection never fetches upstream data.
|
|
13
|
+
const ROCM_REVISION = '14f8138863403a26e0caef6671cfab9b09aa636e'
|
|
14
|
+
const MESA_REVISION = '3e2d8517b897026377267c09975db83525d2fc95'
|
|
15
|
+
const LLVM_REVISION = '2f3f97fb72606f51ece7d64d6a7a73c9eaad8978'
|
|
16
|
+
const LINUX_REVISION = '7d0a66e4bb9081d75c82ec4957c50034cb0ea449'
|
|
17
|
+
const LIBDRM_REVISION = 'f198c21dfcb89127083413dd4779a13b3f9a6507'
|
|
18
|
+
|
|
19
|
+
const SOURCES = {
|
|
20
|
+
rocm_specs: {
|
|
21
|
+
repository: 'https://github.com/ROCm/ROCm',
|
|
22
|
+
revision: ROCM_REVISION,
|
|
23
|
+
path: 'docs/reference/gpu-arch-specs.rst',
|
|
24
|
+
url: `https://raw.githubusercontent.com/ROCm/ROCm/${ROCM_REVISION}/docs/reference/gpu-arch-specs.rst`
|
|
25
|
+
},
|
|
26
|
+
mesa_pci_ids: {
|
|
27
|
+
repository: 'https://gitlab.freedesktop.org/mesa/mesa',
|
|
28
|
+
revision: MESA_REVISION,
|
|
29
|
+
path: 'include/pci_ids/radeonsi_pci_ids.h',
|
|
30
|
+
url: `https://gitlab.freedesktop.org/mesa/mesa/-/raw/${MESA_REVISION}/include/pci_ids/radeonsi_pci_ids.h`
|
|
31
|
+
},
|
|
32
|
+
mesa_families: {
|
|
33
|
+
repository: 'https://gitlab.freedesktop.org/mesa/mesa',
|
|
34
|
+
revision: MESA_REVISION,
|
|
35
|
+
path: 'src/amd/common/amd_family.c',
|
|
36
|
+
url: `https://gitlab.freedesktop.org/mesa/mesa/-/raw/${MESA_REVISION}/src/amd/common/amd_family.c`
|
|
37
|
+
},
|
|
38
|
+
mesa_family_names: {
|
|
39
|
+
repository: 'https://gitlab.freedesktop.org/mesa/mesa',
|
|
40
|
+
revision: MESA_REVISION,
|
|
41
|
+
path: 'src/amd/common/amd_family.h',
|
|
42
|
+
url: `https://gitlab.freedesktop.org/mesa/mesa/-/raw/${MESA_REVISION}/src/amd/common/amd_family.h`
|
|
43
|
+
},
|
|
44
|
+
llvm_processors: {
|
|
45
|
+
repository: 'https://github.com/llvm/llvm-project',
|
|
46
|
+
revision: LLVM_REVISION,
|
|
47
|
+
path: 'llvm/lib/Target/AMDGPU/GCNProcessors.td',
|
|
48
|
+
url: `https://raw.githubusercontent.com/llvm/llvm-project/${LLVM_REVISION}/llvm/lib/Target/AMDGPU/GCNProcessors.td`
|
|
49
|
+
},
|
|
50
|
+
linux_pci_ids: {
|
|
51
|
+
repository: 'https://github.com/torvalds/linux',
|
|
52
|
+
revision: LINUX_REVISION,
|
|
53
|
+
path: 'drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c',
|
|
54
|
+
url: `https://raw.githubusercontent.com/torvalds/linux/${LINUX_REVISION}/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c`
|
|
55
|
+
},
|
|
56
|
+
libdrm_names: {
|
|
57
|
+
repository: 'https://gitlab.freedesktop.org/mesa/libdrm',
|
|
58
|
+
revision: LIBDRM_REVISION,
|
|
59
|
+
path: 'data/amdgpu.ids',
|
|
60
|
+
url: `https://gitlab.freedesktop.org/mesa/libdrm/-/raw/${LIBDRM_REVISION}/data/amdgpu.ids`
|
|
61
|
+
}
|
|
62
|
+
}
|
|
9
63
|
|
|
10
64
|
const repoRoot = path.resolve(__dirname, '..')
|
|
11
65
|
|
|
@@ -33,6 +87,10 @@ function fetchText(url) {
|
|
|
33
87
|
})
|
|
34
88
|
}
|
|
35
89
|
|
|
90
|
+
function sha256(value) {
|
|
91
|
+
return crypto.createHash('sha256').update(value).digest('hex')
|
|
92
|
+
}
|
|
93
|
+
|
|
36
94
|
function canonicalKey(value) {
|
|
37
95
|
let key = normalize_model(value)
|
|
38
96
|
let previous
|
|
@@ -95,25 +153,243 @@ function parseGpuSpecs(rst) {
|
|
|
95
153
|
return entries
|
|
96
154
|
}
|
|
97
155
|
|
|
156
|
+
function parseLlvmProcessorTargets(tablegen) {
|
|
157
|
+
const processors = {}
|
|
158
|
+
const processorPattern = /def\s*:\s*ProcessorModel<"([^"]+)"([\s\S]*?)>;/g
|
|
159
|
+
let processorMatch
|
|
160
|
+
while ((processorMatch = processorPattern.exec(tablegen))) {
|
|
161
|
+
const name = processorMatch[1].toLowerCase()
|
|
162
|
+
const version = processorMatch[2].match(
|
|
163
|
+
/FeatureISAVersion([0-9]+)_([0-9a-f]+)_([0-9a-f]+)\.Features/i
|
|
164
|
+
)
|
|
165
|
+
if (!version) continue
|
|
166
|
+
const target = /^gfx[0-9a-f]+$/.test(name)
|
|
167
|
+
? name
|
|
168
|
+
: `gfx${version[1]}${version[2]}${version[3]}`.toLowerCase()
|
|
169
|
+
processors[name] = target
|
|
170
|
+
}
|
|
171
|
+
return processors
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function parseMesaFamilyTargets(source, processorTargets) {
|
|
175
|
+
const start = source.indexOf('const char *ac_get_llvm_processor_name')
|
|
176
|
+
const end = source.indexOf('const char *ac_get_ip_type_string', start)
|
|
177
|
+
if (start < 0 || end < 0) {
|
|
178
|
+
throw new Error('Mesa ac_get_llvm_processor_name function was not found')
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const body = source.slice(start, end)
|
|
182
|
+
const familyTargets = {}
|
|
183
|
+
const returnPattern = /((?:\s*case\s+CHIP_[A-Z0-9_]+:\s*)+)\s*return\s+"([^"]+)";/g
|
|
184
|
+
let returnMatch
|
|
185
|
+
while ((returnMatch = returnPattern.exec(body))) {
|
|
186
|
+
const processor = returnMatch[2].toLowerCase()
|
|
187
|
+
const target = /^gfx[0-9a-f]+$/.test(processor)
|
|
188
|
+
? processor
|
|
189
|
+
: processorTargets[processor]
|
|
190
|
+
if (!target || !/^gfx[0-9a-f]+$/.test(target)) continue
|
|
191
|
+
|
|
192
|
+
const familyPattern = /case\s+CHIP_([A-Z0-9_]+):/g
|
|
193
|
+
let familyMatch
|
|
194
|
+
while ((familyMatch = familyPattern.exec(returnMatch[1]))) {
|
|
195
|
+
familyTargets[familyMatch[1]] = target
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return familyTargets
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function parseMesaFamilyAliases(source) {
|
|
202
|
+
const aliases = {}
|
|
203
|
+
const aliasPattern = /CHIP_([A-Z0-9_]+),[^\n]*formerly\s+"([^"]+)"/gi
|
|
204
|
+
let aliasMatch
|
|
205
|
+
while ((aliasMatch = aliasPattern.exec(source))) {
|
|
206
|
+
const current = aliasMatch[1].toUpperCase()
|
|
207
|
+
const former = aliasMatch[2].toUpperCase().replace(/[^A-Z0-9]+/g, '_')
|
|
208
|
+
aliases[former] = current
|
|
209
|
+
}
|
|
210
|
+
return aliases
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function addPciEntry(entries, device, family, target) {
|
|
214
|
+
if (!target) return
|
|
215
|
+
const key = `${AMD_VENDOR_ID}:${device.toLowerCase()}`
|
|
216
|
+
const previous = entries[key]
|
|
217
|
+
if (previous && previous.target !== target) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
`Conflicting targets for ${key}: ${previous.target} (${previous.family}) and ${target} (${family})`
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
entries[key] = { target, family }
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function parseMesaPciTargets(source, familyTargets, entries = {}) {
|
|
226
|
+
const pciPattern = /CHIPSET\(0x([0-9a-f]{4}),\s*([A-Z0-9_]+)\)/gi
|
|
227
|
+
let pciMatch
|
|
228
|
+
while ((pciMatch = pciPattern.exec(source))) {
|
|
229
|
+
const device = pciMatch[1].toLowerCase()
|
|
230
|
+
const family = pciMatch[2].toUpperCase()
|
|
231
|
+
addPciEntry(entries, device, family, familyTargets[family])
|
|
232
|
+
}
|
|
233
|
+
return entries
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function parseLinuxPciTargets(source, familyTargets, familyAliases, entries = {}) {
|
|
237
|
+
const pciPattern = /\{\s*0x1002,\s*0x([0-9a-f]{4}),[^\n]*\bCHIP_([A-Z0-9_]+)/gi
|
|
238
|
+
let pciMatch
|
|
239
|
+
while ((pciMatch = pciPattern.exec(source))) {
|
|
240
|
+
const device = pciMatch[1].toLowerCase()
|
|
241
|
+
const sourceFamily = pciMatch[2].toUpperCase()
|
|
242
|
+
const family = familyAliases[sourceFamily] || sourceFamily
|
|
243
|
+
addPciEntry(entries, device, family, familyTargets[family])
|
|
244
|
+
}
|
|
245
|
+
return entries
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function parseLibdrmRevisionTargets(source, modelTargets, pciTargets) {
|
|
249
|
+
const entries = {}
|
|
250
|
+
const rowPattern = /^([0-9a-f]{4}),\s*([0-9a-f]{2}),\s*(.+?)\s*$/gim
|
|
251
|
+
let rowMatch
|
|
252
|
+
while ((rowMatch = rowPattern.exec(source))) {
|
|
253
|
+
const device = rowMatch[1].toLowerCase()
|
|
254
|
+
const revision = rowMatch[2].toLowerCase()
|
|
255
|
+
const model = rowMatch[3].trim()
|
|
256
|
+
const modelTarget = modelTargets[canonicalKey(model)]
|
|
257
|
+
const pciTarget = pciTargets[`${AMD_VENDOR_ID}:${device}`]
|
|
258
|
+
if (pciTarget && modelTarget && pciTarget.target !== modelTarget) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
`Conflicting family/model targets for ${device}:${revision} (${model}): ` +
|
|
261
|
+
`${pciTarget.target} and ${modelTarget}`
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
const target = pciTarget ? pciTarget.target : modelTarget
|
|
265
|
+
if (target) {
|
|
266
|
+
entries[`${AMD_VENDOR_ID}:${device}:${revision}`] = { target, model }
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return entries
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function sourceMetadata(definition, contents) {
|
|
273
|
+
return {
|
|
274
|
+
repository: definition.repository,
|
|
275
|
+
revision: definition.revision,
|
|
276
|
+
path: definition.path,
|
|
277
|
+
url: definition.url,
|
|
278
|
+
sha256: sha256(contents)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function sortedObject(entries) {
|
|
283
|
+
return Object.fromEntries(Object.entries(entries).sort(([a], [b]) => a.localeCompare(b)))
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function buildOutputs(contents) {
|
|
287
|
+
const modelTargets = parseGpuSpecs(contents.rocm_specs)
|
|
288
|
+
const processorTargets = parseLlvmProcessorTargets(contents.llvm_processors)
|
|
289
|
+
const familyTargets = parseMesaFamilyTargets(contents.mesa_families, processorTargets)
|
|
290
|
+
const familyAliases = parseMesaFamilyAliases(contents.mesa_family_names)
|
|
291
|
+
const pciTargets = parseMesaPciTargets(contents.mesa_pci_ids, familyTargets)
|
|
292
|
+
parseLinuxPciTargets(
|
|
293
|
+
contents.linux_pci_ids,
|
|
294
|
+
familyTargets,
|
|
295
|
+
familyAliases,
|
|
296
|
+
pciTargets
|
|
297
|
+
)
|
|
298
|
+
const revisionTargets = parseLibdrmRevisionTargets(
|
|
299
|
+
contents.libdrm_names,
|
|
300
|
+
modelTargets,
|
|
301
|
+
pciTargets
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
const required = {
|
|
305
|
+
'1002:731f': 'gfx1010',
|
|
306
|
+
'1002:7340': 'gfx1012',
|
|
307
|
+
'1002:73bf': 'gfx1030',
|
|
308
|
+
'1002:73ff': 'gfx1032'
|
|
309
|
+
}
|
|
310
|
+
for (const [key, target] of Object.entries(required)) {
|
|
311
|
+
if (!pciTargets[key] || pciTargets[key].target !== target) {
|
|
312
|
+
throw new Error(`Pinned sources did not produce required mapping ${key} -> ${target}`)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
models: {
|
|
318
|
+
source: SOURCES.rocm_specs.url,
|
|
319
|
+
source_revision: SOURCES.rocm_specs.revision,
|
|
320
|
+
source_sha256: sha256(contents.rocm_specs),
|
|
321
|
+
generated_by: GENERATOR,
|
|
322
|
+
entries: sortedObject(modelTargets)
|
|
323
|
+
},
|
|
324
|
+
pci: {
|
|
325
|
+
generated_by: GENERATOR,
|
|
326
|
+
sources: [
|
|
327
|
+
sourceMetadata(SOURCES.mesa_pci_ids, contents.mesa_pci_ids),
|
|
328
|
+
sourceMetadata(SOURCES.mesa_families, contents.mesa_families),
|
|
329
|
+
sourceMetadata(SOURCES.mesa_family_names, contents.mesa_family_names),
|
|
330
|
+
sourceMetadata(SOURCES.llvm_processors, contents.llvm_processors),
|
|
331
|
+
sourceMetadata(SOURCES.linux_pci_ids, contents.linux_pci_ids),
|
|
332
|
+
sourceMetadata(SOURCES.libdrm_names, contents.libdrm_names)
|
|
333
|
+
],
|
|
334
|
+
revision_entries: sortedObject(revisionTargets),
|
|
335
|
+
entries: sortedObject(pciTargets)
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
98
340
|
function writeJson(relativePath, data) {
|
|
99
341
|
const file = path.join(repoRoot, relativePath)
|
|
100
342
|
fs.writeFileSync(file, `${JSON.stringify(data, null, 2)}\n`)
|
|
101
343
|
}
|
|
102
344
|
|
|
345
|
+
function checkJson(relativePath, data) {
|
|
346
|
+
const file = path.join(repoRoot, relativePath)
|
|
347
|
+
const expected = `${JSON.stringify(data, null, 2)}\n`
|
|
348
|
+
if (fs.readFileSync(file, 'utf8') !== expected) {
|
|
349
|
+
throw new Error(`${relativePath} is stale; run npm run update:amd-gfx-targets`)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
103
353
|
async function main() {
|
|
104
|
-
const
|
|
105
|
-
|
|
354
|
+
const contents = Object.fromEntries(await Promise.all(
|
|
355
|
+
Object.entries(SOURCES).map(async ([name, source]) => [name, await fetchText(source.url)])
|
|
356
|
+
))
|
|
357
|
+
const outputs = buildOutputs(contents)
|
|
358
|
+
const files = [
|
|
359
|
+
['kernel/gpu/amd_gfx_targets.json', outputs.models],
|
|
360
|
+
['kernel/gpu/amd_pci_targets.json', outputs.pci]
|
|
361
|
+
]
|
|
106
362
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
363
|
+
if (process.argv.includes('--check')) {
|
|
364
|
+
for (const [file, data] of files) checkJson(file, data)
|
|
365
|
+
console.log('AMD gfx target data is up to date')
|
|
366
|
+
return
|
|
367
|
+
}
|
|
112
368
|
|
|
113
|
-
|
|
369
|
+
for (const [file, data] of files) writeJson(file, data)
|
|
370
|
+
|
|
371
|
+
console.log(
|
|
372
|
+
`Wrote ${Object.keys(outputs.models.entries).length} AMD model targets and ` +
|
|
373
|
+
`${Object.keys(outputs.pci.entries).length} AMD PCI targets`
|
|
374
|
+
)
|
|
114
375
|
}
|
|
115
376
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
377
|
+
if (require.main === module) {
|
|
378
|
+
main().catch((error) => {
|
|
379
|
+
console.error(error)
|
|
380
|
+
process.exit(1)
|
|
381
|
+
})
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
module.exports = {
|
|
385
|
+
SOURCES,
|
|
386
|
+
buildOutputs,
|
|
387
|
+
checkJson,
|
|
388
|
+
parseGpuSpecs,
|
|
389
|
+
parseLibdrmRevisionTargets,
|
|
390
|
+
parseLinuxPciTargets,
|
|
391
|
+
parseLlvmProcessorTargets,
|
|
392
|
+
parseMesaFamilyAliases,
|
|
393
|
+
parseMesaFamilyTargets,
|
|
394
|
+
parseMesaPciTargets
|
|
395
|
+
}
|