/* ds4_gpu_args.c — shared parser for --gpu-vram and --gpu-devices. * * See ds4_gpu_args.h. The actual CUDA probing for the "auto" branch * lives in ds4_cuda.cu so this .c file never includes * directly (mirroring the rest of the tree's separation between .c and * .cu compilation units). */ #include "ds4_gpu_args.h" #include #include #include #include #include #include #define DS4_GPU_ARGS_DEFAULT_SAFETY_MARGIN ((size_t)512 * 1024 * 1024) /* Helper: copy at most outlen-1 bytes of msg into out and NUL-terminate. */ static void errbuf_set(char *out, size_t outlen, const char *msg) { if (!out || outlen == 0) return; size_t n = strlen(msg); if (n >= outlen) n = outlen - 1; memcpy(out, msg, n); out[n] = '\0'; } static void errbuf_setf(char *out, size_t outlen, const char *fmt, ...) { if (!out || outlen == 0) return; va_list ap; va_start(ap, fmt); vsnprintf(out, outlen, fmt, ap); va_end(ap); } /* Maximum per-entry values. Generous, but small enough to keep all * downstream arithmetic safely inside size_t/int. */ #define DS4_GPU_ARGS_MAX_VRAM_GB 16384 /* 16 TiB per device */ #define DS4_GPU_ARGS_MAX_DEVICE_INDEX 1023 /* CUDA visible-device ID cap */ /* Parse a comma-separated list of nonnegative integers into out_count * entries of out[]. Returns 0 on success, nonzero on parse error. * Empty entries (",,") or trailing/leading commas error. Per-entry * upper bound enforced via max_value (use LONG_MAX for "no cap"). */ static int parse_csv_int_list(const char *s, long *out, int *out_count, int max_count, long max_value, char *errbuf, size_t errbuflen, const char *what) { *out_count = 0; if (!s || !*s) { errbuf_setf(errbuf, errbuflen, "%s: empty value", what); return 1; } const char *p = s; while (*p) { /* Disallow leading whitespace inside the list. */ while (*p == ' ' || *p == '\t') p++; char *end = NULL; errno = 0; long v = strtol(p, &end, 10); if (end == p) { errbuf_setf(errbuf, errbuflen, "%s: not a number: '%s'", what, p); return 1; } if (errno != 0 || v < 0 || v > max_value) { errbuf_setf(errbuf, errbuflen, "%s: out-of-range value: '%s' (allowed 0..%ld)", what, p, max_value); return 1; } if (*out_count >= max_count) { errbuf_setf(errbuf, errbuflen, "%s: too many entries (max %d)", what, max_count); return 1; } out[*out_count] = v; (*out_count)++; while (*end == ' ' || *end == '\t') end++; if (*end == ',') { end++; if (*end == '\0' || *end == ',') { errbuf_setf(errbuf, errbuflen, "%s: empty entry near '%s'", what, end); return 1; } } else if (*end != '\0') { errbuf_setf(errbuf, errbuflen, "%s: junk after value: '%s'", what, end); return 1; } p = end; } if (*out_count == 0) { errbuf_setf(errbuf, errbuflen, "%s: no values parsed", what); return 1; } return 0; } int parse_gpu_vram_arg(const char *vram_arg, const char *devices_arg, ds4_gpu_config *out, bool *out_skip_cuda, char *errbuf, size_t errbuflen) { if (!out || !out_skip_cuda) { errbuf_set(errbuf, errbuflen, "internal: NULL out parameter"); return 1; } *out_skip_cuda = false; /* Caller zeroed *out — just confirm. */ out->n_gpus = 0; out->safety_margin_bytes = DS4_GPU_ARGS_DEFAULT_SAFETY_MARGIN; /* Parse devices_arg first so we have the filter when handling "auto". */ long dev_list[DS4_MAX_GPUS] = {0}; int dev_count = 0; if (devices_arg) { if (parse_csv_int_list(devices_arg, dev_list, &dev_count, DS4_MAX_GPUS, DS4_GPU_ARGS_MAX_DEVICE_INDEX, errbuf, errbuflen, "--gpu-devices") != 0) { return 1; } } /* Case 1: vram_arg unset. */ if (vram_arg == NULL) { if (devices_arg == NULL) { errbuf_set(errbuf, errbuflen, "internal: parse_gpu_vram_arg called with both args NULL"); return 1; } /* --gpu-devices given without --gpu-vram: implicit auto with filter. */ vram_arg = "auto"; } /* Case 2: --gpu-vram 0 — short-circuit. */ if (!strcmp(vram_arg, "0")) { if (devices_arg) { errbuf_set(errbuf, errbuflen, "--gpu-vram 0 conflicts with --gpu-devices"); return 1; } *out_skip_cuda = true; return 0; } /* Case 3: --gpu-vram auto. */ if (!strcmp(vram_arg, "auto")) { #if !defined(DS4_NO_GPU) && !defined(__APPLE__) int filter_buf[DS4_MAX_GPUS]; const int *filter_ptr = NULL; if (dev_count > 0) { for (int i = 0; i < dev_count; i++) filter_buf[i] = (int)dev_list[i]; filter_ptr = filter_buf; } if (ds4_gpu_args_probe_auto_cuda(filter_ptr, dev_count, out, out->safety_margin_bytes, errbuf, errbuflen) != 0) { return 1; } return 0; #else (void)dev_list; (void)dev_count; errbuf_set(errbuf, errbuflen, "--gpu-vram auto requires a CUDA build"); return 1; #endif } /* Case 4: explicit GB budgets — "N[,N...]" */ long vram_list[DS4_MAX_GPUS] = {0}; int vram_count = 0; if (parse_csv_int_list(vram_arg, vram_list, &vram_count, DS4_MAX_GPUS, DS4_GPU_ARGS_MAX_VRAM_GB, errbuf, errbuflen, "--gpu-vram") != 0) { return 1; } if (dev_count > 0 && dev_count != vram_count) { errbuf_setf(errbuf, errbuflen, "--gpu-devices count (%d) does not match --gpu-vram count (%d)", dev_count, vram_count); return 1; } out->n_gpus = vram_count; for (int i = 0; i < vram_count; i++) { int dev_id = (dev_count > 0) ? (int)dev_list[i] : i; out->device_indices[i] = dev_id; out->vram_bytes[i] = (size_t)vram_list[i] * (size_t)1024 * 1024 * 1024; } return 0; } int format_gpu_layout_line(const ds4_gpu_config *cfg, bool was_auto, char *out, size_t outlen) { if (!out || outlen == 0 || !cfg) return -1; /* "ds4: GPU config: N devices [d0,d1,...] requested, budgets G0,G1,... GB; auto=" */ int written = snprintf(out, outlen, "ds4: GPU config: %d device%s [", cfg->n_gpus, cfg->n_gpus == 1 ? "" : "s"); if (written < 0 || (size_t)written >= outlen) return written; for (int i = 0; i < cfg->n_gpus; i++) { int n = snprintf(out + written, outlen - (size_t)written, "%s%d", i ? "," : "", cfg->device_indices[i]); if (n < 0 || (size_t)(written + n) >= outlen) return written + n; written += n; } int n = snprintf(out + written, outlen - (size_t)written, "] requested, budgets "); if (n < 0 || (size_t)(written + n) >= outlen) return written + n; written += n; for (int i = 0; i < cfg->n_gpus; i++) { double gb = (double)cfg->vram_bytes[i] / (1024.0 * 1024.0 * 1024.0); int m = snprintf(out + written, outlen - (size_t)written, "%s%.0f", i ? "," : "", gb); if (m < 0 || (size_t)(written + m) >= outlen) return written + m; written += m; } int t = snprintf(out + written, outlen - (size_t)written, " GB; auto=%s", was_auto ? "true" : "false"); if (t < 0) return t; written += t; return written; }