| #define _GNU_SOURCE |
| #define _FILE_OFFSET_BITS 64 |
| |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <sys/types.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #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; i<op->num; 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; i<child->size_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, }, |
| }; |
| |