Skip to content

Commit c653d1d

Browse files
committed
udpate
1 parent 0042c47 commit c653d1d

9 files changed

Lines changed: 1004 additions & 17 deletions

File tree

lib/qemu

Submodule qemu updated from 96691a3 to f8e2e74

qemu_integration/README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,79 @@ Query counters again from the host with:
180180
python3 ./zettai_host_dcd_gfam_test.py --query
181181
```
182182

183+
### Zettai Type2 tmatmul and CXL.mem ioctl test
184+
185+
The Zettai switch CCI device (`7a74:a123`) creates a guest char device such as
186+
`/dev/zettai_cxl0d003`. The current Linux driver ABI for this device is
187+
`ioctl()`, not `io_uring_cmd`; `/tmp/zettai-qmp.sock` remains a host-side QMP
188+
socket used for bind/add/query orchestration.
189+
190+
Build the guest helper:
191+
192+
```bash
193+
gcc -O2 -Wall -Wextra -o zettai_tmatmul_ctl zettai_tmatmul_ctl.c
194+
```
195+
196+
Check whether QEMU exposed the tmatmul CSR block:
197+
198+
```bash
199+
./zettai_tmatmul_ctl --dev /dev/zettai_cxl0d003 --info
200+
```
201+
202+
If dmesg reports `tmatmul=0` or the tool prints `tmatmul_present=no`, QEMU only
203+
exposed the switch CCI BAR and tmatmul smoke runs will return `ENODEV`. CXL.mem
204+
read/write can still be tested by passing a real nonzero HPA base from a CXL
205+
region or decoder resource:
206+
207+
```bash
208+
cxl list -R -u
209+
./zettai_tmatmul_ctl --dev /dev/zettai_cxl0d003 \
210+
--mem-write --hpa-base 0xYOUR_REGION_RESOURCE --hpa-size 0x10000000 \
211+
--offset 0 --size 4096 --pattern 0x5a
212+
./zettai_tmatmul_ctl --dev /dev/zettai_cxl0d003 \
213+
--mem-read --hpa-base 0xYOUR_REGION_RESOURCE --hpa-size 0x10000000 \
214+
--offset 0 --size 64
215+
```
216+
217+
Once the QEMU Zettai device exposes a BAR large enough for the tmatmul CSR window
218+
at `BAR0 + 0x1c0000`, run:
219+
220+
```bash
221+
./zettai_tmatmul_ctl --dev /dev/zettai_cxl0d003 \
222+
--smoke --hpa-base 0xYOUR_REGION_RESOURCE --hpa-size 0x10000000
223+
```
224+
225+
### Zettai benchmark harness
226+
227+
For a repeatable host-side smoke benchmark, use:
228+
229+
```bash
230+
QEMU_NET_MODE=none \
231+
KERNEL_IMAGE=/path/to/bzImage \
232+
DISK_IMAGE=/path/to/rootfs.img \
233+
./zettai_benchmark.sh --launch --keep-qemu
234+
```
235+
236+
The harness launches QEMU with a QMP socket, binds `cxl-dcd0`, adds a 256 MiB
237+
DCD extent, queries CXLMemSim DCD/GFAM counters, and writes logs under
238+
`build/zettai-bench/`. If QEMU is already running, omit `--launch` and keep the
239+
same `ZETTAI_QMP_SOCKET` value used by `QEMU_EXTRA_ARGS`.
240+
241+
To include the in-guest DCD region setup and Type2 fabric-memory BAR benchmark,
242+
provide SSH access to the guest:
243+
244+
```bash
245+
ZETTAI_GUEST_SSH="ssh root@192.168.122.10" \
246+
ZETTAI_GUEST_DIR=/root/CXLMemSim/qemu_integration \
247+
./zettai_benchmark.sh --guest --run-type2-bench
248+
```
249+
250+
The Type2 benchmark is `guest_libcuda/cxl_bar_benchmark.c`. It discovers the
251+
`cxl-type2` endpoint (`8086:0d92`), reports BAR register and data-region
252+
latency/bandwidth, then exercises the Zettai fabric-memory controls exposed by
253+
QEMU: `DCD_GET_INFO`, optional DCD add/release when free capacity exists,
254+
`GFAM_GET_INFO`, and `MHSLD_GET_INFO/SET_HEAD`.
255+
183256
## Features
184257

185258
- **Cacheline-granular access**: All memory operations are performed at 64-byte cacheline granularity

qemu_integration/guest_libcuda/cxl_bar_benchmark.c

Lines changed: 159 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <dirent.h>
1212
#include <errno.h>
1313
#include <fcntl.h>
14+
#include <inttypes.h>
1415
#include <pthread.h>
1516
#include <sched.h>
1617
#include <stdint.h>
@@ -101,7 +102,9 @@ static void enable_device(const char *bdf) {
101102
snprintf(path, sizeof(path), "/sys/bus/pci/devices/%s/enable", bdf);
102103
int fd = open(path, O_WRONLY);
103104
if (fd >= 0) {
104-
write(fd, "1", 1);
105+
if (write(fd, "1", 1) != 1) {
106+
fprintf(stderr, " Cannot enable %s: %s\n", bdf, strerror(errno));
107+
}
105108
close(fd);
106109
}
107110
}
@@ -118,9 +121,11 @@ static int discover_devices(void) {
118121
continue;
119122
if (read_pci_id(ent->d_name, "device") != CXL_TYPE2_DEVICE)
120123
continue;
124+
if (strlen(ent->d_name) >= sizeof(g_devs[g_num_devices].bdf))
125+
continue;
121126

122127
cxl_dev_t *d = &g_devs[g_num_devices];
123-
strncpy(d->bdf, ent->d_name, sizeof(d->bdf) - 1);
128+
strcpy(d->bdf, ent->d_name);
124129
enable_device(d->bdf);
125130

126131
d->bar2_size = bar_range(d->bdf, 2);
@@ -182,6 +187,49 @@ static double time_ns(void) {
182187
return ts.tv_sec * 1e9 + ts.tv_nsec;
183188
}
184189

190+
static inline uint64_t read_reg64(cxl_dev_t *d, size_t off) { return *(volatile uint64_t *)(d->bar2 + off); }
191+
192+
static inline void write_reg64(cxl_dev_t *d, size_t off, uint64_t val) { *(volatile uint64_t *)(d->bar2 + off) = val; }
193+
194+
static int issue_command(cxl_dev_t *d, uint32_t cmd) {
195+
volatile uint32_t *cmd_reg = (volatile uint32_t *)(d->bar2 + CXL_GPU_REG_CMD);
196+
volatile uint32_t *status_reg = (volatile uint32_t *)(d->bar2 + CXL_GPU_REG_CMD_STATUS);
197+
volatile uint32_t *result_reg = (volatile uint32_t *)(d->bar2 + CXL_GPU_REG_CMD_RESULT);
198+
199+
*cmd_reg = cmd;
200+
__sync_synchronize();
201+
202+
for (int timeout = 100000; timeout > 0; timeout--) {
203+
uint32_t st = *status_reg;
204+
205+
if (st == CXL_GPU_CMD_STATUS_COMPLETE) {
206+
return (int)*result_reg;
207+
}
208+
if (st == CXL_GPU_CMD_STATUS_ERROR) {
209+
return -(int)*result_reg;
210+
}
211+
}
212+
213+
return -ETIMEDOUT;
214+
}
215+
216+
static void measure_command_latency(cxl_dev_t *d, uint32_t cmd, const char *name, int iters) {
217+
double t0;
218+
double t1;
219+
int rc = 0;
220+
221+
t0 = time_ns();
222+
for (int i = 0; i < iters; i++) {
223+
rc = issue_command(d, cmd);
224+
if (rc != CXL_GPU_SUCCESS) {
225+
printf(" %-20s failed: rc=%d\n", name, rc);
226+
return;
227+
}
228+
}
229+
t1 = time_ns();
230+
printf(" %-20s %7.1f ns/op (%d ops)\n", name, (t1 - t0) / iters, iters);
231+
}
232+
185233
/* Benchmark: Register Latency */
186234

187235
static void bench_register_latency(cxl_dev_t *d) {
@@ -453,6 +501,114 @@ static void bench_access_patterns(cxl_dev_t *d) {
453501
}
454502
}
455503

504+
/* Benchmark: DCD / GFAM / MH-SLD Fabric Memory Controls */
505+
506+
static void bench_fabric_memory(cxl_dev_t *d, uint32_t caps) {
507+
uint64_t dcd_total = 0;
508+
uint64_t dcd_alloc = 0;
509+
uint64_t dcd_free = 0;
510+
uint64_t dcd_extents = 0;
511+
uint64_t gfam_hosts = 0;
512+
uint64_t gfam_mappings = 0;
513+
uint64_t gfam_allowed = 0;
514+
uint64_t gfam_denied = 0;
515+
uint64_t mhsld_heads = 0;
516+
uint64_t mhsld_head_id = 0;
517+
int rc;
518+
519+
if (!(caps & (CXL_GPU_CAP_DCD | CXL_GPU_CAP_GFAM | CXL_GPU_CAP_MHSLD))) {
520+
printf("\n--- Fabric Memory Controls: skipped (no DCD/GFAM/MH-SLD caps) ---\n");
521+
return;
522+
}
523+
524+
printf("\n--- Fabric Memory Controls (device %s) ---\n", d->bdf);
525+
526+
if (caps & CXL_GPU_CAP_DCD) {
527+
rc = issue_command(d, CXL_GPU_CMD_DCD_GET_INFO);
528+
if (rc == CXL_GPU_SUCCESS) {
529+
dcd_total = read_reg64(d, CXL_GPU_REG_RESULT0);
530+
dcd_alloc = read_reg64(d, CXL_GPU_REG_RESULT1);
531+
dcd_free = read_reg64(d, CXL_GPU_REG_RESULT2);
532+
dcd_extents = read_reg64(d, CXL_GPU_REG_RESULT3);
533+
printf(" DCD command info: total=%" PRIu64 " alloc=%" PRIu64 " free=%" PRIu64 " extents=%" PRIu64 "\n",
534+
dcd_total, dcd_alloc, dcd_free, dcd_extents);
535+
printf(" DCD status regs: total=%" PRIu64 " alloc=%" PRIu64 " free=%" PRIu64 " extents=%" PRIu64 "\n",
536+
read_reg64(d, CXL_GPU_REG_DCD_TOTAL), read_reg64(d, CXL_GPU_REG_DCD_ALLOCATED),
537+
read_reg64(d, CXL_GPU_REG_DCD_FREE), read_reg64(d, CXL_GPU_REG_DCD_EXTENTS));
538+
measure_command_latency(d, CXL_GPU_CMD_DCD_GET_INFO, "DCD_GET_INFO", 5000);
539+
} else {
540+
printf(" DCD_GET_INFO failed: rc=%d\n", rc);
541+
}
542+
543+
if (dcd_free >= 1024 * 1024) {
544+
write_reg64(d, CXL_GPU_REG_PARAM0, UINT64_MAX);
545+
write_reg64(d, CXL_GPU_REG_PARAM1, 1024 * 1024);
546+
write_reg64(d, CXL_GPU_REG_PARAM2, 0);
547+
rc = issue_command(d, CXL_GPU_CMD_DCD_ADD);
548+
if (rc == CXL_GPU_SUCCESS) {
549+
uint64_t base = read_reg64(d, CXL_GPU_REG_RESULT0);
550+
uint64_t size = read_reg64(d, CXL_GPU_REG_RESULT1);
551+
uint64_t tag = read_reg64(d, CXL_GPU_REG_RESULT2);
552+
553+
printf(" DCD add/release: base=0x%" PRIx64 " size=%" PRIu64 " tag=%" PRIu64 "\n", base, size, tag);
554+
write_reg64(d, CXL_GPU_REG_PARAM0, base);
555+
write_reg64(d, CXL_GPU_REG_PARAM1, size);
556+
write_reg64(d, CXL_GPU_REG_PARAM2, tag);
557+
rc = issue_command(d, CXL_GPU_CMD_DCD_RELEASE);
558+
if (rc != CXL_GPU_SUCCESS) {
559+
printf(" DCD_RELEASE failed after add: rc=%d\n", rc);
560+
}
561+
} else {
562+
printf(" DCD_ADD skipped/failed: rc=%d\n", rc);
563+
}
564+
} else {
565+
printf(" DCD add/release: skipped (no free DCD capacity)\n");
566+
}
567+
}
568+
569+
if (caps & CXL_GPU_CAP_GFAM) {
570+
rc = issue_command(d, CXL_GPU_CMD_GFAM_GET_INFO);
571+
if (rc == CXL_GPU_SUCCESS) {
572+
gfam_hosts = read_reg64(d, CXL_GPU_REG_RESULT0);
573+
gfam_mappings = read_reg64(d, CXL_GPU_REG_RESULT1);
574+
gfam_allowed = read_reg64(d, CXL_GPU_REG_RESULT2);
575+
gfam_denied = read_reg64(d, CXL_GPU_REG_RESULT3);
576+
printf(" GFAM command info: hosts=%" PRIu64 " mappings=%" PRIu64 " allowed=%" PRIu64 " denied=%" PRIu64
577+
"\n",
578+
gfam_hosts, gfam_mappings, gfam_allowed, gfam_denied);
579+
printf(" GFAM status regs: hosts=%" PRIu64 " mappings=%" PRIu64 " denied=%" PRIu64 "\n",
580+
read_reg64(d, CXL_GPU_REG_GFAM_HOSTS), read_reg64(d, CXL_GPU_REG_GFAM_MAPPINGS),
581+
read_reg64(d, CXL_GPU_REG_GFAM_DENIED));
582+
measure_command_latency(d, CXL_GPU_CMD_GFAM_GET_INFO, "GFAM_GET_INFO", 5000);
583+
} else {
584+
printf(" GFAM_GET_INFO failed: rc=%d\n", rc);
585+
}
586+
}
587+
588+
if (caps & CXL_GPU_CAP_MHSLD) {
589+
rc = issue_command(d, CXL_GPU_CMD_MHSLD_GET_INFO);
590+
if (rc == CXL_GPU_SUCCESS) {
591+
mhsld_heads = read_reg64(d, CXL_GPU_REG_RESULT0);
592+
mhsld_head_id = read_reg64(d, CXL_GPU_REG_RESULT1);
593+
printf(" MH-SLD command info: heads=%" PRIu64 " local=%" PRIu64 " reads=%" PRIu64 " writes=%" PRIu64 "\n",
594+
mhsld_heads, mhsld_head_id, read_reg64(d, CXL_GPU_REG_RESULT2), read_reg64(d, CXL_GPU_REG_RESULT3));
595+
printf(" MH-SLD status regs: heads=%" PRIu64 " local=%" PRIu64 " conflicts=%" PRIu64
596+
" invalidations=%" PRIu64 "\n",
597+
read_reg64(d, CXL_GPU_REG_MHSLD_HEADS), read_reg64(d, CXL_GPU_REG_MHSLD_HEAD_ID),
598+
read_reg64(d, CXL_GPU_REG_MHSLD_CONFLICTS), read_reg64(d, CXL_GPU_REG_MHSLD_INV));
599+
measure_command_latency(d, CXL_GPU_CMD_MHSLD_GET_INFO, "MHSLD_GET_INFO", 5000);
600+
} else {
601+
printf(" MHSLD_GET_INFO failed: rc=%d\n", rc);
602+
}
603+
604+
if (mhsld_heads > 0) {
605+
write_reg64(d, CXL_GPU_REG_PARAM0, mhsld_head_id);
606+
rc = issue_command(d, CXL_GPU_CMD_MHSLD_SET_HEAD);
607+
printf(" MH-SLD set-head: head=%" PRIu64 " rc=%d\n", mhsld_head_id, rc);
608+
}
609+
}
610+
}
611+
456612
/* Benchmark: Dual-Device Concurrent Access */
457613

458614
typedef struct {
@@ -561,6 +717,7 @@ int main(void) {
561717

562718
bench_register_latency(d);
563719
bench_cmd_latency(d);
720+
bench_fabric_memory(d, caps);
564721
bench_data_region_bw(d);
565722
bench_access_patterns(d);
566723
bench_bar4_bulk_bw(d);

qemu_integration/guest_libcuda/cxl_gpu_cmd.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@
6363
#define CXL_GPU_CAP_DMA_ENGINE (1 << 2) /* Hardware DMA engine available */
6464
#define CXL_GPU_CAP_COHERENT_POOL (1 << 3) /* Coherent shared memory pool */
6565
#define CXL_GPU_CAP_DEVICE_BIAS (1 << 4) /* Device-biased directory mode */
66+
#define CXL_GPU_CAP_DCD (1 << 5) /* Dynamic Capacity Device model */
67+
#define CXL_GPU_CAP_GFAM (1 << 6) /* Global Fabric Attached Memory */
68+
#define CXL_GPU_CAP_MHSLD (1 << 7) /* Multi-headed SLD coherency */
6669

6770
/* Magic number */
6871
#define CXL_GPU_MAGIC 0x43584C32 /* "CXL2" */
@@ -151,6 +154,16 @@ typedef enum {
151154
/* Coherency statistics commands */
152155
CXL_GPU_CMD_COH_GET_STATS = 0xB0, /* Get coherency statistics */
153156
CXL_GPU_CMD_COH_RESET_STATS = 0xB1, /* Reset coherency statistics */
157+
158+
/* DCD/GFAM/MH-SLD fabric-memory commands */
159+
CXL_GPU_CMD_DCD_ADD = 0xC0, /* params: base, size, tag */
160+
CXL_GPU_CMD_DCD_RELEASE = 0xC1, /* params: base, size, tag */
161+
CXL_GPU_CMD_DCD_GET_INFO = 0xC2, /* results: total, alloc, free */
162+
CXL_GPU_CMD_GFAM_GRANT = 0xC8, /* params: host, base, size, perms */
163+
CXL_GPU_CMD_GFAM_REVOKE = 0xC9, /* params: host, base, size */
164+
CXL_GPU_CMD_GFAM_GET_INFO = 0xCA, /* results: hosts, mappings, deny */
165+
CXL_GPU_CMD_MHSLD_GET_INFO = 0xD0, /* results: heads, current, stats */
166+
CXL_GPU_CMD_MHSLD_SET_HEAD = 0xD1, /* params: head_id */
154167
} CXLGPUCommand;
155168

156169
/* Coherent pool register offsets (in GPU command region) */
@@ -160,6 +173,19 @@ typedef enum {
160173
#define CXL_GPU_REG_COH_DIR_SIZE 0x0318 /* Directory size (entries) */
161174
#define CXL_GPU_REG_COH_DIR_USED 0x0320 /* Directory used entries */
162175

176+
/* DCD/GFAM/MH-SLD status registers */
177+
#define CXL_GPU_REG_DCD_TOTAL 0x0330 /* DCD total capacity */
178+
#define CXL_GPU_REG_DCD_ALLOCATED 0x0338 /* DCD allocated capacity */
179+
#define CXL_GPU_REG_DCD_FREE 0x0340 /* DCD free capacity */
180+
#define CXL_GPU_REG_DCD_EXTENTS 0x0348 /* Active DCD extent count */
181+
#define CXL_GPU_REG_GFAM_HOSTS 0x0350 /* Configured GFAM hosts */
182+
#define CXL_GPU_REG_GFAM_MAPPINGS 0x0358 /* Active GFAM mappings */
183+
#define CXL_GPU_REG_GFAM_DENIED 0x0360 /* Denied GFAM accesses */
184+
#define CXL_GPU_REG_MHSLD_HEADS 0x0370 /* MH-SLD head count */
185+
#define CXL_GPU_REG_MHSLD_HEAD_ID 0x0378 /* Local MH-SLD head id */
186+
#define CXL_GPU_REG_MHSLD_CONFLICTS 0x0380 /* MH-SLD coherency conflicts */
187+
#define CXL_GPU_REG_MHSLD_INV 0x0388 /* MH-SLD invalidations */
188+
163189
/* Bias mode constants.
164190
* Legacy values 0/1 remain valid and imply 64B flit/cache-line granularity.
165191
* Extended encodings use low 8 bits for home domain and upper bits for
@@ -177,6 +203,13 @@ typedef enum {
177203
#define CXL_BIAS_MODE(encoded) ((uint8_t)((uint64_t)(encoded) & CXL_BIAS_MODE_MASK))
178204
#define CXL_BIAS_GRAN(encoded) ((uint64_t)(encoded) >> CXL_BIAS_GRAN_SHIFT)
179205

206+
/* DCD/GFAM permission bits */
207+
#define CXL_DCD_PERM_READ (1 << 0)
208+
#define CXL_DCD_PERM_WRITE (1 << 1)
209+
#define CXL_DCD_PERM_ATOMIC (1 << 2)
210+
#define CXL_DCD_PERM_SHARED (1 << 3)
211+
#define CXL_DCD_PERM_ALL (CXL_DCD_PERM_READ | CXL_DCD_PERM_WRITE | CXL_DCD_PERM_ATOMIC | CXL_DCD_PERM_SHARED)
212+
180213
/* P2P register offsets (in GPU command region) */
181214
#define CXL_GPU_REG_P2P_NUM_PEERS 0x0200 /* Number of discovered peers */
182215
#define CXL_GPU_REG_P2P_PEER_ID 0x0204 /* Current peer ID for queries */

qemu_integration/launch_qemu_vcs_dcd_gfam.sh

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,13 @@ VM_MAXMEM=${VM_MAXMEM:-32G}
3535
VM_SMP=${VM_SMP:-4}
3636
DISK_IMAGE=${DISK_IMAGE:-./qemu.img}
3737
DISK_FORMAT=${DISK_FORMAT:-auto}
38-
KERNEL_IMAGE=${KERNEL_IMAGE:-./bzImage}
38+
KERNEL_IMAGE=${KERNEL_IMAGE:-/root/linux-cxl-type2/arch/x86/boot/bzImage}
3939
KERNEL_APPEND=${KERNEL_APPEND:-"root=/dev/vda rw console=ttyS0,115200 nokaslr"}
4040
QEMU_DISK_DEVICE=${QEMU_DISK_DEVICE:-"virtio-blk-pci,drive=bootdisk,bus=pcie.0,id=bootdisk0"}
4141
QEMU_NET_MODE=${QEMU_NET_MODE:-none}
4242
QEMU_NETDEV=${QEMU_NETDEV:-}
4343
QEMU_NET_DEVICE=${QEMU_NET_DEVICE:-"virtio-net-pci,netdev=net0"}
44+
QEMU_STDIO_SIGNAL=${QEMU_STDIO_SIGNAL:-off}
4445

4546
if [[ ! -x "$QEMU_BINARY" ]]; then
4647
echo "QEMU binary not found or not executable: $QEMU_BINARY" >&2
@@ -136,6 +137,13 @@ if [[ -n "$QEMU_NETDEV" ]]; then
136137
net_args=(-netdev "$QEMU_NETDEV" -device "$QEMU_NET_DEVICE")
137138
fi
138139

140+
console_args=(
141+
-display none
142+
-chardev "stdio,id=char0,signal=$QEMU_STDIO_SIGNAL,mux=on"
143+
-serial chardev:char0
144+
-mon chardev=char0,mode=readline
145+
)
146+
139147
mkdir -p "$RUN_DIR"
140148
truncate -s "$CXL_DC_SIZE" "$RUN_DIR/cxl-dcd0.raw"
141149
truncate -s "$CXL_DC_SIZE" "$RUN_DIR/cxl-dcd1.raw"
@@ -286,5 +294,5 @@ exec "$QEMU_BINARY" \
286294
-device cxl-type3,volatile-dc-memdev=cxl-mem1,lsa=cxl-lsa1,id=cxl-dcd1,sn=102,num-dc-regions=8,vcs=zettai0,dsppb=1,memsim-dcd=on,memsim-gfam=on,memsim-gfam-host-id=1 \
287295
-device cxl-type3,volatile-dc-memdev=cxl-mem2,lsa=cxl-lsa2,id=cxl-dcd2,sn=103,num-dc-regions=8,vcs=zettai0,dsppb=2,memsim-dcd=on,memsim-gfam=on,memsim-gfam-host-id=2 \
288296
-device cxl-type3,volatile-dc-memdev=cxl-mem3,lsa=cxl-lsa3,id=cxl-dcd3,sn=104,num-dc-regions=8,vcs=zettai0,dsppb=3,memsim-dcd=on,memsim-gfam=on,memsim-gfam-host-id=3 \
289-
-nographic \
297+
"${console_args[@]}" \
290298
"${extra_args[@]}"

0 commit comments

Comments
 (0)