summaryrefslogtreecommitdiffstats
path: root/tools/testing/selftests/powerpc/nx-gzip/gzfht_test.c
blob: 095195a25687e7837f98bf55d4bf3a4901e04870 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
// SPDX-License-Identifier: GPL-2.0-or-later

/* P9 gzip sample code for demonstrating the P9 NX hardware interface.
 * Not intended for productive uses or for performance or compression
 * ratio measurements.  For simplicity of demonstration, this sample
 * code compresses in to fixed Huffman blocks only (Deflate btype=1)
 * and has very simple memory management.  Dynamic Huffman blocks
 * (Deflate btype=2) are more involved as detailed in the user guide.
 * Note also that /dev/crypto/gzip, VAS and skiboot support are
 * required.
 *
 * Copyright 2020 IBM Corp.
 *
 * https://github.com/libnxz/power-gzip for zlib api and other utils
 *
 * Author: Bulent Abali <abali@us.ibm.com>
 *
 * Definitions of acronyms used here. See
 * P9 NX Gzip Accelerator User's Manual for details:
 * https://github.com/libnxz/power-gzip/blob/develop/doc/power_nx_gzip_um.pdf
 *
 * adler/crc: 32 bit checksums appended to stream tail
 * ce:       completion extension
 * cpb:      coprocessor parameter block (metadata)
 * crb:      coprocessor request block (command)
 * csb:      coprocessor status block (status)
 * dht:      dynamic huffman table
 * dde:      data descriptor element (address, length)
 * ddl:      list of ddes
 * dh/fh:    dynamic and fixed huffman types
 * fc:       coprocessor function code
 * histlen:  history/dictionary length
 * history:  sliding window of up to 32KB of data
 * lzcount:  Deflate LZ symbol counts
 * rembytecnt: remaining byte count
 * sfbt:     source final block type; last block's type during decomp
 * spbc:     source processed byte count
 * subc:     source unprocessed bit count
 * tebc:     target ending bit count; valid bits in the last byte
 * tpbc:     target processed byte count
 * vas:      virtual accelerator switch; the user mode interface
 */

#define _ISOC11_SOURCE	// For aligned_alloc()
#define _DEFAULT_SOURCE	// For endian.h

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/fcntl.h>
#include <sys/mman.h>
#include <endian.h>
#include <bits/endian.h>
#include <sys/ioctl.h>
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include "utils.h"
#include "nxu.h"
#include "nx.h"

int nx_dbg;
FILE *nx_gzip_log;

#define NX_MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
#define FNAME_MAX 1024
#define FEXT ".nx.gz"

#define SYSFS_MAX_REQ_BUF_PATH "devices/vio/ibm,compression-v1/nx_gzip_caps/req_max_processed_len"

/*
 * LZ counts returned in the user supplied nx_gzip_crb_cpb_t structure.
 */
static int compress_fht_sample(char *src, uint32_t srclen, char *dst,
				uint32_t dstlen, int with_count,
				struct nx_gzip_crb_cpb_t *cmdp, void *handle)
{
	uint32_t fc;

	assert(!!cmdp);

	put32(cmdp->crb, gzip_fc, 0);  /* clear */
	fc = (with_count) ? GZIP_FC_COMPRESS_RESUME_FHT_COUNT :
			    GZIP_FC_COMPRESS_RESUME_FHT;
	putnn(cmdp->crb, gzip_fc, fc);
	putnn(cmdp->cpb, in_histlen, 0); /* resuming with no history */
	memset((void *) &cmdp->crb.csb, 0, sizeof(cmdp->crb.csb));

	/* Section 6.6 programming notes; spbc may be in two different
	 * places depending on FC.
	 */
	if (!with_count)
		put32(cmdp->cpb, out_spbc_comp, 0);
	else
		put32(cmdp->cpb, out_spbc_comp_with_count, 0);

	/* Figure 6-3 6-4; CSB location */
	put64(cmdp->crb, csb_address, 0);
	put64(cmdp->crb, csb_address,
	      (uint64_t) &cmdp->crb.csb & csb_address_mask);

	/* Source direct dde (scatter-gather list) */
	clear_dde(cmdp->crb.source_dde);
	putnn(cmdp->crb.source_dde, dde_count, 0);
	put32(cmdp->crb.source_dde, ddebc, srclen);
	put64(cmdp->crb.source_dde, ddead, (uint64_t) src);

	/* Target direct dde (scatter-gather list) */
	clear_dde(cmdp->crb.target_dde);
	putnn(cmdp->crb.target_dde, dde_count, 0);
	put32(cmdp->crb.target_dde, ddebc, dstlen);
	put64(cmdp->crb.target_dde, ddead, (uint64_t) dst);

	/* Submit the crb, the job descriptor, to the accelerator */
	return nxu_submit_job(cmdp, handle);
}

/*
 * Prepares a blank no filename no timestamp gzip header and returns
 * the number of bytes written to buf.
 * Gzip specification at https://tools.ietf.org/html/rfc1952
 */
int gzip_header_blank(char *buf)
{
	int i = 0;

	buf[i++] = 0x1f; /* ID1 */
	buf[i++] = 0x8b; /* ID2 */
	buf[i++] = 0x08; /* CM  */
	buf[i++] = 0x00; /* FLG */
	buf[i++] = 0x00; /* MTIME */
	buf[i++] = 0x00; /* MTIME */
	buf[i++] = 0x00; /* MTIME */
	buf[i++] = 0x00; /* MTIME */
	buf[i++] = 0x04; /* XFL 4=fastest */
	buf[i++] = 0x03; /* OS UNIX */

	return i;
}

/* Caller must free the allocated buffer return nonzero on error. */
int read_alloc_input_file(char *fname, char **buf, size_t *bufsize)
{
	struct stat statbuf;
	FILE *fp;
	char *p;
	size_t num_bytes;

	if (stat(fname, &statbuf)) {
		perror(fname);
		return(-1);
	}
	fp = fopen(fname, "r");
	if (fp == NULL) {
		perror(fname);
		return(-1);
	}
	assert(NULL != (p = (char *) malloc(statbuf.st_size)));
	num_bytes = fread(p, 1, statbuf.st_size, fp);
	if (ferror(fp) || (num_bytes != statbuf.st_size)) {
		perror(fname);
		return(-1);
	}
	*buf = p;
	*bufsize = num_bytes;
	return 0;
}

/* Returns nonzero on error */
int write_output_file(char *fname, char *buf, size_t bufsize)
{
	FILE *fp;
	size_t num_bytes;

	fp = fopen(fname, "w");
	if (fp == NULL) {
		perror(fname);
		return(-1);
	}
	num_bytes = fwrite(buf, 1, bufsize, fp);
	if (ferror(fp) || (num_bytes != bufsize)) {
		perror(fname);
		return(-1);
	}
	fclose(fp);
	return 0;
}

/*
 * Z_SYNC_FLUSH as described in zlib.h.
 * Returns number of appended bytes
 */
int append_sync_flush(char *buf, int tebc, int final)
{
	uint64_t flush;
	int shift = (tebc & 0x7);

	if (tebc > 0) {
		/* Last byte is partially full */
		buf = buf - 1;
		*buf = *buf & (unsigned char) ((1<<tebc)-1);
	} else
		*buf = 0;
	flush = ((0x1ULL & final) << shift) | *buf;
	shift = shift + 3; /* BFINAL and BTYPE written */
	shift = (shift <= 8) ? 8 : 16;
	flush |= (0xFFFF0000ULL) << shift; /* Zero length block */
	shift = shift + 32;
	while (shift > 0) {
		*buf++ = (unsigned char) (flush & 0xffULL);
		flush = flush >> 8;
		shift = shift - 8;
	}
	return(((tebc > 5) || (tebc == 0)) ? 5 : 4);
}

/*
 * Final deflate block bit.  This call assumes the block
 * beginning is byte aligned.
 */
static void set_bfinal(void *buf, int bfinal)
{
	char *b = buf;

	if (bfinal)
		*b = *b | (unsigned char) 0x01;
	else
		*b = *b & (unsigned char) 0xfe;
}

int compress_file(int argc, char **argv, void *handle)
{
	char *inbuf, *outbuf, *srcbuf, *dstbuf;
	char outname[FNAME_MAX];
	uint32_t srclen, dstlen;
	uint32_t flushlen, chunk;
	size_t inlen, outlen, dsttotlen, srctotlen;
	uint32_t crc, spbc, tpbc, tebc;
	int lzcounts = 0;
	int cc;
	int num_hdr_bytes;
	struct nx_gzip_crb_cpb_t *cmdp;
	uint32_t pagelen = 65536;
	int fault_tries = NX_MAX_FAULTS;
	char buf[32];

	cmdp = (void *)(uintptr_t)
		aligned_alloc(sizeof(struct nx_gzip_crb_cpb_t),
			      sizeof(struct nx_gzip_crb_cpb_t));

	if (argc != 2) {
		fprintf(stderr, "usage: %s <fname>\n", argv[0]);
		exit(-1);
	}
	if (read_alloc_input_file(argv[1], &inbuf, &inlen))
		exit(-1);
	fprintf(stderr, "file %s read, %ld bytes\n", argv[1], inlen);

	/* Generous output buffer for header/trailer */
	outlen = 2 * inlen + 1024;

	assert(NULL != (outbuf = (char *)malloc(outlen)));
	nxu_touch_pages(outbuf, outlen, pagelen, 1);

	/*
	 * On PowerVM, the hypervisor defines the maximum request buffer
	 * size is defined and this value is available via sysfs.
	 */
	if (!read_sysfs_file(SYSFS_MAX_REQ_BUF_PATH, buf, sizeof(buf))) {
		chunk = atoi(buf);
	} else {
		/* sysfs entry is not available on PowerNV */
		/* Compress piecemeal in smallish chunks */
		chunk = 1<<22;
	}

	/* Write the gzip header to the stream */
	num_hdr_bytes = gzip_header_blank(outbuf);
	dstbuf    = outbuf + num_hdr_bytes;
	outlen    = outlen - num_hdr_bytes;
	dsttotlen = num_hdr_bytes;

	srcbuf    = inbuf;
	srctotlen = 0;

	/* Init the CRB, the coprocessor request block */
	memset(&cmdp->crb, 0, sizeof(cmdp->crb));

	/* Initial gzip crc32 */
	put32(cmdp->cpb, in_crc, 0);

	while (inlen > 0) {

		/* Submit chunk size source data per job */
		srclen = NX_MIN(chunk, inlen);
		/* Supply large target in case data expands */
		dstlen = NX_MIN(2*srclen, outlen);

		/* Page faults are handled by the user code */

		/* Fault-in pages; an improved code wouldn't touch so
		 * many pages but would try to estimate the
		 * compression ratio and adjust both the src and dst
		 * touch amounts.
		 */
		nxu_touch_pages(cmdp, sizeof(struct nx_gzip_crb_cpb_t), pagelen,
				1);
		nxu_touch_pages(srcbuf, srclen, pagelen, 0);
		nxu_touch_pages(dstbuf, dstlen, pagelen, 1);

		cc = compress_fht_sample(
			srcbuf, srclen,
			dstbuf, dstlen,
			lzcounts, cmdp, handle);

		if (cc != ERR_NX_OK && cc != ERR_NX_TPBC_GT_SPBC &&
		    cc != ERR_NX_AT_FAULT) {
			fprintf(stderr, "nx error: cc= %d\n", cc);
			exit(-1);
		}

		/* Page faults are handled by the user code */
		if (cc == ERR_NX_AT_FAULT) {
			NXPRT(fprintf(stderr, "page fault: cc= %d, ", cc));
			NXPRT(fprintf(stderr, "try= %d, fsa= %08llx\n",
				  fault_tries,
				  (unsigned long long) cmdp->crb.csb.fsaddr));
			fault_tries--;
			if (fault_tries > 0) {
				continue;
			} else {
				fprintf(stderr, "error: cannot progress; ");
				fprintf(stderr, "too many faults\n");
				exit(-1);
			}
		}

		fault_tries = NX_MAX_FAULTS; /* Reset for the next chunk */

		inlen     = inlen - srclen;
		srcbuf    = srcbuf + srclen;
		srctotlen = srctotlen + srclen;

		/* Two possible locations for spbc depending on the function
		 * code.
		 */
		spbc = (!lzcounts) ? get32(cmdp->cpb, out_spbc_comp) :
			get32(cmdp->cpb, out_spbc_comp_with_count);
		assert(spbc == srclen);

		/* Target byte count */
		tpbc = get32(cmdp->crb.csb, tpbc);
		/* Target ending bit count */
		tebc = getnn(cmdp->cpb, out_tebc);
		NXPRT(fprintf(stderr, "compressed chunk %d ", spbc));
		NXPRT(fprintf(stderr, "to %d bytes, tebc= %d\n", tpbc, tebc));

		if (inlen > 0) { /* More chunks to go */
			set_bfinal(dstbuf, 0);
			dstbuf    = dstbuf + tpbc;
			dsttotlen = dsttotlen + tpbc;
			outlen    = outlen - tpbc;
			/* Round up to the next byte with a flush
			 * block; do not set the BFINAqL bit.
			 */
			flushlen  = append_sync_flush(dstbuf, tebc, 0);
			dsttotlen = dsttotlen + flushlen;
			outlen    = outlen - flushlen;
			dstbuf    = dstbuf + flushlen;
			NXPRT(fprintf(stderr, "added sync_flush %d bytes\n",
					flushlen));
		} else {  /* Done */
			/* Set the BFINAL bit of the last block per Deflate
			 * specification.
			 */
			set_bfinal(dstbuf, 1);
			dstbuf    = dstbuf + tpbc;
			dsttotlen = dsttotlen + tpbc;
			outlen    = outlen - tpbc;
		}

		/* Resuming crc32 for the next chunk */
		crc = get32(cmdp->cpb, out_crc);
		put32(cmdp->cpb, in_crc, crc);
		crc = be32toh(crc);
	}

	/* Append crc32 and ISIZE to the end */
	memcpy(dstbuf, &crc, 4);
	memcpy(dstbuf+4, &srctotlen, 4);
	dsttotlen = dsttotlen + 8;
	outlen    = outlen - 8;

	assert(FNAME_MAX > (strlen(argv[1]) + strlen(FEXT)));
	strcpy(outname, argv[1]);
	strcat(outname, FEXT);
	if (write_output_file(outname, outbuf, dsttotlen)) {
		fprintf(stderr, "write error: %s\n", outname);
		exit(-1);
	}

	fprintf(stderr, "compressed %ld to %ld bytes total, ", srctotlen,
		dsttotlen);
	fprintf(stderr, "crc32 checksum = %08x\n", crc);

	if (inbuf != NULL)
		free(inbuf);

	if (outbuf != NULL)
		free(outbuf);

	return 0;
}

int main(int argc, char **argv)
{
	int rc;
	struct sigaction act;
	void *handle;

	nx_dbg = 0;
	nx_gzip_log = NULL;
	act.sa_handler = 0;
	act.sa_sigaction = nxu_sigsegv_handler;
	act.sa_flags = SA_SIGINFO;
	act.sa_restorer = 0;
	sigemptyset(&act.sa_mask);
	sigaction(SIGSEGV, &act, NULL);

	handle = nx_function_begin(NX_FUNC_COMP_GZIP, 0);
	if (!handle) {
		fprintf(stderr, "Unable to init NX, errno %d\n", errno);
		exit(-1);
	}

	rc = compress_file(argc, argv, handle);

	nx_function_end(handle);

	return rc;
}