#define _GNU_SOURCE #define _FILE_OFFSET_BITS 64 #include #include #include #include #include #include "dev.h" #include "vm.h" static inline res_t *res_ptr(res_t r) { return (res_t *)((unsigned long)r._p & ~7); } static inline enum resulttype res_type(res_t r) { return (enum resulttype)((unsigned long)r._p & 7); } static inline res_t to_res(res_t *_p, enum resulttype t) { return (res_t) { ._p = (res_t *)(((unsigned long)_p & ~7) | (t & 7)) }; } static const res_t res_null = { }; struct syntax { enum opcode opcode; const char *name; struct operation *(*function)(struct operation *op, struct device *dev, off_t off, off_t max, size_t len); enum { P_NUM = 1, P_VAL = 2, P_STRING = 4, P_AGGREGATE = 8, P_ATOM = 16, } param; }; static struct syntax syntax[]; int verbose = 0; struct operation *call(struct operation *op, struct device *dev, off_t off, off_t max, size_t len) { struct operation *next; if (!op) return_err("internal error: NULL operation\n"); pr_debug("call %s %lld %lld %ld\n", syntax[op->code].name, (long long)off, (long long)max, (long)len); if (op->code > O_MAX) return_err("illegal command code %d\n", op->code); if (!(syntax[op->code].param & P_NUM) != !op->num) return_err("need .num= argument\n"); if (!(syntax[op->code].param & P_VAL) != !op->val) return_err("need .param= argument\n"); if (!(syntax[op->code].param & P_STRING) != !op->string) return_err("need .string= argument\n"); if (!(syntax[op->code].param & P_AGGREGATE) != !op->aggregate) return_err("need .aggregate= argument\n"); if (op->num) { res_t *data; if (res_ptr(op->result)) return_err("%s already has result\n", syntax[op->code].name); data = calloc(sizeof (res_t), op->num); if (!data) return_err("out of memory"); op->result = to_res(data, R_NONE); op->r_type = R_ARRAY; } next = syntax[op->code].function(op, dev, off, max, len); if (!next) return_err("from %s\n", syntax[op->code].name); return next; } static struct operation *call_propagate(struct operation *op, struct device *dev, off_t off, off_t max, size_t len, struct operation *this) { struct operation *next; next = call(op, dev, off, max, len); this->result = op->result; this->size_x = op->size_x; this->size_y = op->size_y; this->r_type = op->r_type; op->result = res_null; op->size_x = op->size_y = 0; op->r_type = R_NONE; return next; } static struct operation *call_aggregate(struct operation *op, struct device *dev, off_t off, off_t max, size_t len, struct operation *this) { struct operation *next; res_t *res = res_ptr(this->result); enum resulttype type = res_type(this->result); next = call(op, dev, off, max, len); if (!next) return NULL; if (this->size_x >= this->num) return_err("array too small for %d entries\n", this->size_x); res[this->size_x] = op->result; /* no result */ if (op->r_type == R_NONE) return next; this->size_x++; /* first data in this aggregation: set type */ if (type == R_NONE) { type = op->r_type; this->result = to_res(res, type); } if (type != op->r_type) { return_err("cannot aggregate return type %d with %d\n", type, op->r_type); } if (op->r_type == R_ARRAY) { if (this->size_y && this->size_y != op->size_x) return_err("cannot aggregate different size arrays (%d, %d)\n", this->size_y, op->size_x); if (op->size_y) return_err("cannot aggregate three-dimensional array\n"); this->size_y = op->size_x; op->size_x = op->size_y = 0; } op->r_type = R_NONE; op->result = res_null; return next; } static struct operation *nop(struct operation *op, struct device *dev, off_t off, off_t max, size_t len) { return_err("command not implemented\n"); } static struct operation *do_read(struct operation *op, struct device *dev, off_t off, off_t max, size_t len) { op->result.l = time_read(dev, off, len); op->r_type = R_NS; return op+1; } static struct operation *do_write_zero(struct operation *op, struct device *dev, off_t off, off_t max, size_t len) { op->result.l = time_write(dev, off, len, WBUF_ZERO); op->r_type = R_NS; return op+1; } static struct operation *do_write_one(struct operation *op, struct device *dev, off_t off, off_t max, size_t len) { op->result.l = time_write(dev, off, len, WBUF_ONE); op->r_type = R_NS; return op+1; } static struct operation *do_write_rand(struct operation *op, struct device *dev, off_t off, off_t max, size_t len) { op->result.l = time_write(dev, off, len, WBUF_RAND); op->r_type = R_NS; return op+1; } static struct operation *do_erase(struct operation *op, struct device *dev, off_t off, off_t max, size_t len) { op->result.l = time_erase(dev, off, len); op->r_type = R_NS; return op+1; } static struct operation *length_or_offs(struct operation *op, struct device *dev, off_t off, off_t max, size_t len) { op->result.l = (op->code == O_LENGTH) ? (long long)len : off; op->r_type = R_BYTE; return op+1; } static res_t format_value(res_t val, enum resulttype type, unsigned int size_x, unsigned int size_y) { long long l = val.l; unsigned int x; res_t *res; res_t out; switch (type) { case R_ARRAY: res = res_ptr(val); for (x = 0; x < size_x; x++) { res[x] = format_value(res[x], res_type(val), size_y, 0); if (res[x].s == res_null.s) return res_null; } if (res_type(val) == R_ARRAY) out = val; else out = to_res(res_ptr(val), R_STRING); return out; case R_BYTE: if (l < 1024) snprintf(out.s, 8, "%0lldB", l); else if (l < 1024 * 1024) snprintf(out.s, 8, "%0.3gKiB", l / 1024.0); else if (l < 1024 * 1024 * 1024) snprintf(out.s, 8, "%0.3gMiB", l / (1024.0 * 1024.0)); else snprintf(out.s, 8, "%0.4gGiB", l / (1024.0 * 1024.0 * 1024.0)); break; case R_BPS: if (l < 1000) snprintf(out.s, 8, "%0lldB/s", l); else if (l < 1000 * 1000) snprintf(out.s, 8, "%.03gK/s", l / 1000.0); else if (l < 1000 * 1000 * 1000) snprintf(out.s, 8, "%.03gM/s", l / (1000.0 * 1000.0)); else snprintf(out.s, 8, "%.04gG/s", l / (1000.0 * 1000.0 * 1000.0)); break; case R_NS: if (l < 1000) snprintf(out.s, 8, "%lldns", l); else if (l < 1000 * 1000) snprintf(out.s, 8, "%.3gµs", l / 1000.0); else if (l < 1000 * 1000 * 1000) snprintf(out.s, 8, "%.3gms", l / 1000000.0); else snprintf(out.s, 8, "%.4gs", l / 1000000000.0); break; default: return res_null; } for (x = strlen(out.s); x<7; x++) out.s[x] = ' '; out.s[7] = '\0'; return out; } static struct operation *format(struct operation *op, struct device *dev, off_t off, off_t max, size_t len) { struct operation *next; next = call_propagate(op+1, dev, off, max, len, op); op->result = format_value(op->result, op->r_type, op->size_x, op->size_y); if (op->result.s == res_null.s) return NULL; if (op->r_type != R_ARRAY) op->r_type = R_STRING; return next; } static struct operation *print_string(struct operation *op, struct device *dev, off_t off, off_t max, size_t len) { printf("%s", op->string); return op+1; } static void *print_value(res_t val, enum resulttype type, unsigned int size_x, unsigned int size_y) { unsigned int x; res_t *res; switch (type) { case R_ARRAY: res = res_ptr(val); for (x=0; x < size_x; x++) { if (!print_value(res[x], res_type(val), size_y, 0)) return_err("cannot print array of type %d\n", res_type(val)); printf(size_y ? "\n" : " "); } break; case R_BYTE: case R_NS: case R_BPS: printf("%lld ", val.l); break; case R_STRING: printf("%s ", val.s); break; default: return NULL; } return (void *)1; } static struct operation *print_val(struct operation *op, struct device *dev, off_t off, off_t max, size_t len) { struct operation *next; next = call_propagate(op+1, dev, off, max, len, op); if (!next) return NULL; if (!print_value(op->result, op->r_type, op->size_x, op->size_y)) return_err("cannot print value of type %d\n", op->r_type); return next; } static struct operation *newline(struct operation *op, struct device *dev, off_t off, off_t max, size_t len) { printf("\n"); return op+1; } static res_t bytespersec_one(res_t res, size_t bytes, enum resulttype type, unsigned int size_x, unsigned int size_y) { if (type == R_NS) res.l = 1000000000ll * bytes / res.l; else if (type == R_ARRAY) { res_t *array = res_ptr(res); type = res_type(res); unsigned int x; for (x = 0; x < size_x; x++) array[x] = bytespersec_one(array[x], bytes, type, size_y, 0); if (type == R_NS) res = to_res(array, R_BPS); } else { res = res_null; } return res; } static struct operation *bytespersec(struct operation *op, struct device *dev, off_t off, off_t max, size_t len) { struct operation *next; next = call_propagate(op+1, dev, off, max, len, op); op->result = bytespersec_one(op->result, len, op->r_type, op->size_x, op->size_y); if (op->result.l == res_null.l) return_err("invalid data, type %d\n", op->r_type); if (op->r_type == R_NS) op->r_type = R_BPS; return next; } static struct operation *sequence(struct operation *op, struct device *dev, off_t off, off_t max, size_t len) { unsigned int i; struct operation *next = op+1; for (i=0; inum; i++) { next = call_aggregate(next, dev, off, max, len, op); if (!next) return NULL; } /* immediately fold sequences with a single result */ if (op->size_x == 1) { op->r_type = res_type(op->result); op->result = res_ptr(op->result)[0]; op->size_x = op->size_y; op->size_y = 0; } if (next && next->code != O_END) return_err("sequence needs to end with END command\n"); return next+1; } static struct operation *len_fixed(struct operation *op, struct device *dev, off_t off, off_t max, size_t len) { return call_propagate(op+1, dev, off, max, op->val, op); } static struct operation *len_pow2(struct operation *op, struct device *dev, off_t off, off_t max, size_t len) { unsigned int i; struct operation *next = op+1; if (!len) len = 1; if (op->val > 0) { for (i = 0; i < op->num && next; i++) next = call_aggregate(op+1, dev, off, max, len * op->val << i, op); } else { for (i = op->num; i>0 && next; i--) next = call_aggregate(op+1, dev, off, max, len * (-op->val/2) << i, op); } return next; } static struct operation *off_fixed(struct operation *op, struct device *dev, off_t off, off_t max, size_t len) { return call_propagate(op+1, dev, off + op->val, max, len, op); } static struct operation *off_lin(struct operation *op, struct device *dev, off_t off, off_t max, size_t len) { struct operation *next = op+1; unsigned int i; unsigned int num, val; if (op->val == -1) { if (len == 0 || max < (off_t)len) return_err("cannot fill %lld bytes with %ld byte chunks\n", (long long)max, (long)len); num = max/len; val = max/num; } else { val = op->val; num = op->num; } for (i = 0; i < num && next; i++) next = call_aggregate(op+1, dev, off + i * val, max, len, op); return next; } /* * Linear feedback shift register * * We use this to randomize the block positions for random-access * tests. Unlike real random data, we know that within 2^bits * accesses, every possible value up to 2^bits will be seen * exactly once, with the exception of zero, for which we have * a special treatment. */ static int lfsr(unsigned short v, unsigned int bits) { unsigned short bit; if (v >= (1 << bits)) { fprintf(stderr, "lfsr: internal error\n"); exit(-1); } if (v == (((1 << bits) - 1) & 0xace1)) return 0; if (v == 0) v = ((1 << bits) - 1) & 0xace1; switch (bits) { case 8: /* x^8 + x^6 + x^5 + x^4 + 1 */ bit = ((v >> 0) ^ (v >> 2) ^ (v >> 3) ^ (v >> 4)) & 1; break; case 9: /* x9 + x5 + 1 */ bit = ((v >> 0) ^ (v >> 4)) & 1; break; case 10: /* x10 + x7 + 1 */ bit = ((v >> 0) ^ (v >> 3)) & 1; break; case 11: /* x11 + x9 + 1 */ bit = ((v >> 0) ^ (v >> 2)) & 1; break; case 12: bit = ((v >> 0) ^ (v >> 1) ^ (v >> 2) ^ (v >> 8)) & 1; break; case 13: /* x^13 + x^12 + x^11 + x^8 + 1 */ bit = ((v >> 0) ^ (v >> 1) ^ (v >> 2) ^ (v >> 5)) & 1; break; case 14: /* x^14 + x^13 + x^12 + x^2 + 1 */ bit = ((v >> 0) ^ (v >> 1) ^ (v >> 2) ^ (v >> 12)) & 1; break; case 15: /* x^15 + x^14 + 1 */ bit = ((v >> 0) ^ (v >> 1) ) & 1; break; case 16: /* x^16 + x^14 + x^13 + x^11 + 1 */ bit = ((v >> 0) ^ (v >> 2) ^ (v >> 3) ^ (v >> 5) ) & 1; break; default: fprintf(stderr, "lfsr: internal error\n"); exit(-1); } return v >> 1 | bit << (bits - 1); } static struct operation *off_rand(struct operation *op, struct device *dev, off_t off, off_t max, size_t len) { struct operation *next = op+1; unsigned int i; unsigned int num, val; unsigned int pos = 0, bits = 0; if (op->val == -1) { if (len == 0 || max < (off_t)len) return_err("cannot fill %lld bytes with %ld byte chunks\n", (long long)max, (long)len); num = max/len; val = max/num; } else { val = op->val; num = op->num; } for (i = num; i > 0; i /= 2) bits++; if (bits < 8) bits = 8; for (i = 0; i < num && next; i++) { do { pos = lfsr(pos, bits); } while (pos >= num); next = call_aggregate(op+1, dev, off + pos * val, max, len, op); } return next; } static struct operation *repeat(struct operation *op, struct device *dev, off_t off, off_t max, size_t len) { struct operation *next = op+1; unsigned int i; for (i = 0; i < op->num && next; i++) next = call_aggregate(op+1, dev, off, max, len, op); return next; } static res_t do_reduce_int(int num, res_t *input, int aggregate) { int i; res_t result = { .l = 0 }; for (i = 0; i < num; i++) { switch (aggregate) { case A_MINIMUM: if (!result.l || result.l > input[i].l) result.l = input[i].l; break; case A_MAXIMUM: if (!result.l || result.l < input[i].l) result.l = input[i].l; break; case A_AVERAGE: case A_TOTAL: result.l += input[i].l; break; } } if (aggregate == A_AVERAGE) result.l /= num; return result; } static struct operation *reduce(struct operation *op, struct device *dev, off_t off, off_t max, size_t len) { struct operation *next, *child; unsigned int i; enum resulttype type; res_t *in; child = op+1; next = call(child, dev, off, max, len); if (!next) return NULL; /* single value */ if (child->r_type != R_ARRAY || child->size_x == 0) return_err("cannot reduce scalar further, type %d, size %d\n", child->r_type, child->size_y); /* data does not fit */ if (child->size_y > op->num) return_err("target array too short\n"); /* FIXME: is this necessary? */ /* one-dimensional array */ if (child->size_y == 0) { if (res_type(child->result) != R_NS && res_type(child->result) != R_BPS) return_err("cannot reduce type %d\n", res_type(child->result)); op->result = do_reduce_int(child->size_x, res_ptr(child->result), op->aggregate); op->size_x = op->size_y = 0; op->r_type = res_type(child->result); goto clear_child; } /* two-dimensional array */ in = res_ptr(child->result); if (res_type(child->result) != R_ARRAY) return_err("inconsistent array contents\n"); type = res_type(in[0]); for (i=0; isize_x; i++) { if (res_type(in[i]) != type) return_err("cannot combine type %d and %d\n", res_type(in[i]), type); res_ptr(op->result)[i] = do_reduce_int(child->size_y, res_ptr(in[i]), op->aggregate); } op->result = to_res(res_ptr(op->result), type); op->size_x = child->size_y; op->size_y = 0; op->r_type = R_ARRAY; clear_child: child->result = res_null; child->size_x = child->size_y = 0; child->r_type = R_NONE; return next; } static struct operation *drop(struct operation *op, struct device *dev, off_t off, off_t max, size_t len) { struct operation *next, *child; child = op+1; next = call(child, dev, off, max, len); if (!next) return NULL; child->result = res_null; child->r_type = R_NONE; child->size_x = child->size_y = 0; return next; } static struct syntax syntax[] = { { O_END, "END", nop, }, { O_READ, "READ", do_read, }, { O_WRITE_ZERO, "WRITE_ZERO", do_write_zero, }, { O_WRITE_ONE, "WRITE_ONE", do_write_one, }, { O_WRITE_RAND, "WRITE_RAND", do_write_rand, }, { O_ERASE, "ERASE", do_erase, }, { O_LENGTH, "LENGTH", length_or_offs }, { O_OFFSET, "OFFSET", length_or_offs, }, { O_PRINT, "PRINT", print_string, P_STRING }, { O_PRINTF, "PRINTF", print_val, }, { O_FORMAT, "FORMAT", format, }, { O_NEWLINE, "NEWLINE", newline, }, { O_BPS, "BPS", bytespersec, }, { O_SEQUENCE, "SEQUENCE", sequence, P_NUM }, { O_REPEAT, "REPEAT", repeat, P_NUM }, { O_OFF_FIXED, "OFF_FIXED", off_fixed, P_VAL }, { O_OFF_POW2, "OFF_POW2", nop, P_NUM | P_VAL }, { O_OFF_LIN, "OFF_LIN", off_lin, P_NUM | P_VAL }, { O_OFF_RAND, "OFF_RAND", off_rand, P_NUM | P_VAL }, { O_LEN_FIXED, "LEN_FIXED", len_fixed, P_VAL }, { O_LEN_POW2, "LEN_POW2", len_pow2, P_NUM | P_VAL }, { O_MAX_POW2, "MAX_POW2", nop, P_NUM | P_VAL }, { O_MAX_LIN, "MAX_LIN", nop, P_NUM | P_VAL }, { O_REDUCE, "REDUCE", reduce, P_AGGREGATE }, { O_DROP, "DROP", drop, }, };