/* SPDX-License-Identifier: GPL-2.0 */ 
 | 
/* 
 | 
 * Traceprobe fetch helper inlines 
 | 
 */ 
 | 
  
 | 
static nokprobe_inline void 
 | 
fetch_store_raw(unsigned long val, struct fetch_insn *code, void *buf) 
 | 
{ 
 | 
    switch (code->size) { 
 | 
    case 1: 
 | 
        *(u8 *)buf = (u8)val; 
 | 
        break; 
 | 
    case 2: 
 | 
        *(u16 *)buf = (u16)val; 
 | 
        break; 
 | 
    case 4: 
 | 
        *(u32 *)buf = (u32)val; 
 | 
        break; 
 | 
    case 8: 
 | 
        //TBD: 32bit signed 
 | 
        *(u64 *)buf = (u64)val; 
 | 
        break; 
 | 
    default: 
 | 
        *(unsigned long *)buf = val; 
 | 
    } 
 | 
} 
 | 
  
 | 
static nokprobe_inline void 
 | 
fetch_apply_bitfield(struct fetch_insn *code, void *buf) 
 | 
{ 
 | 
    switch (code->basesize) { 
 | 
    case 1: 
 | 
        *(u8 *)buf <<= code->lshift; 
 | 
        *(u8 *)buf >>= code->rshift; 
 | 
        break; 
 | 
    case 2: 
 | 
        *(u16 *)buf <<= code->lshift; 
 | 
        *(u16 *)buf >>= code->rshift; 
 | 
        break; 
 | 
    case 4: 
 | 
        *(u32 *)buf <<= code->lshift; 
 | 
        *(u32 *)buf >>= code->rshift; 
 | 
        break; 
 | 
    case 8: 
 | 
        *(u64 *)buf <<= code->lshift; 
 | 
        *(u64 *)buf >>= code->rshift; 
 | 
        break; 
 | 
    } 
 | 
} 
 | 
  
 | 
/* 
 | 
 * These functions must be defined for each callsite. 
 | 
 * Return consumed dynamic data size (>= 0), or error (< 0). 
 | 
 * If dest is NULL, don't store result and return required dynamic data size. 
 | 
 */ 
 | 
static int 
 | 
process_fetch_insn(struct fetch_insn *code, struct pt_regs *regs, 
 | 
           void *dest, void *base); 
 | 
static nokprobe_inline int fetch_store_strlen(unsigned long addr); 
 | 
static nokprobe_inline int 
 | 
fetch_store_string(unsigned long addr, void *dest, void *base); 
 | 
static nokprobe_inline int fetch_store_strlen_user(unsigned long addr); 
 | 
static nokprobe_inline int 
 | 
fetch_store_string_user(unsigned long addr, void *dest, void *base); 
 | 
static nokprobe_inline int 
 | 
probe_mem_read(void *dest, void *src, size_t size); 
 | 
static nokprobe_inline int 
 | 
probe_mem_read_user(void *dest, void *src, size_t size); 
 | 
  
 | 
/* From the 2nd stage, routine is same */ 
 | 
static nokprobe_inline int 
 | 
process_fetch_insn_bottom(struct fetch_insn *code, unsigned long val, 
 | 
               void *dest, void *base) 
 | 
{ 
 | 
    struct fetch_insn *s3 = NULL; 
 | 
    int total = 0, ret = 0, i = 0; 
 | 
    u32 loc = 0; 
 | 
    unsigned long lval = val; 
 | 
  
 | 
stage2: 
 | 
    /* 2nd stage: dereference memory if needed */ 
 | 
    do { 
 | 
        if (code->op == FETCH_OP_DEREF) { 
 | 
            lval = val; 
 | 
            ret = probe_mem_read(&val, (void *)val + code->offset, 
 | 
                         sizeof(val)); 
 | 
        } else if (code->op == FETCH_OP_UDEREF) { 
 | 
            lval = val; 
 | 
            ret = probe_mem_read_user(&val, 
 | 
                 (void *)val + code->offset, sizeof(val)); 
 | 
        } else 
 | 
            break; 
 | 
        if (ret) 
 | 
            return ret; 
 | 
        code++; 
 | 
    } while (1); 
 | 
  
 | 
    s3 = code; 
 | 
stage3: 
 | 
    /* 3rd stage: store value to buffer */ 
 | 
    if (unlikely(!dest)) { 
 | 
        if (code->op == FETCH_OP_ST_STRING) { 
 | 
            ret = fetch_store_strlen(val + code->offset); 
 | 
            code++; 
 | 
            goto array; 
 | 
        } else if (code->op == FETCH_OP_ST_USTRING) { 
 | 
            ret += fetch_store_strlen_user(val + code->offset); 
 | 
            code++; 
 | 
            goto array; 
 | 
        } else 
 | 
            return -EILSEQ; 
 | 
    } 
 | 
  
 | 
    switch (code->op) { 
 | 
    case FETCH_OP_ST_RAW: 
 | 
        fetch_store_raw(val, code, dest); 
 | 
        break; 
 | 
    case FETCH_OP_ST_MEM: 
 | 
        probe_mem_read(dest, (void *)val + code->offset, code->size); 
 | 
        break; 
 | 
    case FETCH_OP_ST_UMEM: 
 | 
        probe_mem_read_user(dest, (void *)val + code->offset, code->size); 
 | 
        break; 
 | 
    case FETCH_OP_ST_STRING: 
 | 
        loc = *(u32 *)dest; 
 | 
        ret = fetch_store_string(val + code->offset, dest, base); 
 | 
        break; 
 | 
    case FETCH_OP_ST_USTRING: 
 | 
        loc = *(u32 *)dest; 
 | 
        ret = fetch_store_string_user(val + code->offset, dest, base); 
 | 
        break; 
 | 
    default: 
 | 
        return -EILSEQ; 
 | 
    } 
 | 
    code++; 
 | 
  
 | 
    /* 4th stage: modify stored value if needed */ 
 | 
    if (code->op == FETCH_OP_MOD_BF) { 
 | 
        fetch_apply_bitfield(code, dest); 
 | 
        code++; 
 | 
    } 
 | 
  
 | 
array: 
 | 
    /* the last stage: Loop on array */ 
 | 
    if (code->op == FETCH_OP_LP_ARRAY) { 
 | 
        total += ret; 
 | 
        if (++i < code->param) { 
 | 
            code = s3; 
 | 
            if (s3->op != FETCH_OP_ST_STRING && 
 | 
                s3->op != FETCH_OP_ST_USTRING) { 
 | 
                dest += s3->size; 
 | 
                val += s3->size; 
 | 
                goto stage3; 
 | 
            } 
 | 
            code--; 
 | 
            val = lval + sizeof(char *); 
 | 
            if (dest) { 
 | 
                dest += sizeof(u32); 
 | 
                *(u32 *)dest = update_data_loc(loc, ret); 
 | 
            } 
 | 
            goto stage2; 
 | 
        } 
 | 
        code++; 
 | 
        ret = total; 
 | 
    } 
 | 
  
 | 
    return code->op == FETCH_OP_END ? ret : -EILSEQ; 
 | 
} 
 | 
  
 | 
/* Sum up total data length for dynamic arraies (strings) */ 
 | 
static nokprobe_inline int 
 | 
__get_data_size(struct trace_probe *tp, struct pt_regs *regs) 
 | 
{ 
 | 
    struct probe_arg *arg; 
 | 
    int i, len, ret = 0; 
 | 
  
 | 
    for (i = 0; i < tp->nr_args; i++) { 
 | 
        arg = tp->args + i; 
 | 
        if (unlikely(arg->dynamic)) { 
 | 
            len = process_fetch_insn(arg->code, regs, NULL, NULL); 
 | 
            if (len > 0) 
 | 
                ret += len; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    return ret; 
 | 
} 
 | 
  
 | 
/* Store the value of each argument */ 
 | 
static nokprobe_inline void 
 | 
store_trace_args(void *data, struct trace_probe *tp, struct pt_regs *regs, 
 | 
         int header_size, int maxlen) 
 | 
{ 
 | 
    struct probe_arg *arg; 
 | 
    void *base = data - header_size; 
 | 
    void *dyndata = data + tp->size; 
 | 
    u32 *dl;    /* Data location */ 
 | 
    int ret, i; 
 | 
  
 | 
    for (i = 0; i < tp->nr_args; i++) { 
 | 
        arg = tp->args + i; 
 | 
        dl = data + arg->offset; 
 | 
        /* Point the dynamic data area if needed */ 
 | 
        if (unlikely(arg->dynamic)) 
 | 
            *dl = make_data_loc(maxlen, dyndata - base); 
 | 
        ret = process_fetch_insn(arg->code, regs, dl, base); 
 | 
        if (unlikely(ret < 0 && arg->dynamic)) { 
 | 
            *dl = make_data_loc(0, dyndata - base); 
 | 
        } else { 
 | 
            dyndata += ret; 
 | 
            maxlen -= ret; 
 | 
        } 
 | 
    } 
 | 
} 
 | 
  
 | 
static inline int 
 | 
print_probe_args(struct trace_seq *s, struct probe_arg *args, int nr_args, 
 | 
         u8 *data, void *field) 
 | 
{ 
 | 
    void *p; 
 | 
    int i, j; 
 | 
  
 | 
    for (i = 0; i < nr_args; i++) { 
 | 
        struct probe_arg *a = args + i; 
 | 
  
 | 
        trace_seq_printf(s, " %s=", a->name); 
 | 
        if (likely(!a->count)) { 
 | 
            if (!a->type->print(s, data + a->offset, field)) 
 | 
                return -ENOMEM; 
 | 
            continue; 
 | 
        } 
 | 
        trace_seq_putc(s, '{'); 
 | 
        p = data + a->offset; 
 | 
        for (j = 0; j < a->count; j++) { 
 | 
            if (!a->type->print(s, p, field)) 
 | 
                return -ENOMEM; 
 | 
            trace_seq_putc(s, j == a->count - 1 ? '}' : ','); 
 | 
            p += a->type->size; 
 | 
        } 
 | 
    } 
 | 
    return 0; 
 | 
} 
 |