root/drivers/media/usb/pvrusb2/pvrusb2-ctrl.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 *
 *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
 */

#include "pvrusb2-ctrl.h"
#include "pvrusb2-hdw-internal.h"
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mutex.h>


static int pvr2_ctrl_range_check(struct pvr2_ctrl *cptr,int val)
{
        if (cptr->info->check_value) {
                if (!cptr->info->check_value(cptr,val)) return -ERANGE;
        } else if (cptr->info->type == pvr2_ctl_enum) {
                if (val < 0) return -ERANGE;
                if (val >= cptr->info->def.type_enum.count) return -ERANGE;
        } else {
                int lim;
                lim = cptr->info->def.type_int.min_value;
                if (cptr->info->get_min_value) {
                        cptr->info->get_min_value(cptr,&lim);
                }
                if (val < lim) return -ERANGE;
                lim = cptr->info->def.type_int.max_value;
                if (cptr->info->get_max_value) {
                        cptr->info->get_max_value(cptr,&lim);
                }
                if (val > lim) return -ERANGE;
        }
        return 0;
}


/* Set the given control. */
int pvr2_ctrl_set_value(struct pvr2_ctrl *cptr,int val)
{
        return pvr2_ctrl_set_mask_value(cptr,~0,val);
}


/* Set/clear specific bits of the given control. */
int pvr2_ctrl_set_mask_value(struct pvr2_ctrl *cptr,int mask,int val)
{
        int ret = 0;
        if (!cptr) return -EINVAL;
        LOCK_TAKE(cptr->hdw->big_lock); do {
                if (cptr->info->set_value) {
                        if (cptr->info->type == pvr2_ctl_bitmask) {
                                mask &= cptr->info->def.type_bitmask.valid_bits;
                        } else if ((cptr->info->type == pvr2_ctl_int)||
                                   (cptr->info->type == pvr2_ctl_enum)) {
                                ret = pvr2_ctrl_range_check(cptr,val);
                                if (ret < 0) break;
                        } else if (cptr->info->type != pvr2_ctl_bool) {
                                break;
                        }
                        ret = cptr->info->set_value(cptr,mask,val);
                } else {
                        ret = -EPERM;
                }
        } while(0); LOCK_GIVE(cptr->hdw->big_lock);
        return ret;
}


/* Get the current value of the given control. */
int pvr2_ctrl_get_value(struct pvr2_ctrl *cptr,int *valptr)
{
        int ret = 0;
        if (!cptr) return -EINVAL;
        LOCK_TAKE(cptr->hdw->big_lock); do {
                ret = cptr->info->get_value(cptr,valptr);
        } while(0); LOCK_GIVE(cptr->hdw->big_lock);
        return ret;
}


/* Retrieve control's type */
enum pvr2_ctl_type pvr2_ctrl_get_type(struct pvr2_ctrl *cptr)
{
        if (!cptr) return pvr2_ctl_int;
        return cptr->info->type;
}


/* Retrieve control's maximum value (int type) */
int pvr2_ctrl_get_max(struct pvr2_ctrl *cptr)
{
        int ret = 0;
        if (!cptr) return 0;
        LOCK_TAKE(cptr->hdw->big_lock); do {
                if (cptr->info->get_max_value) {
                        cptr->info->get_max_value(cptr,&ret);
                } else if (cptr->info->type == pvr2_ctl_int) {
                        ret = cptr->info->def.type_int.max_value;
                }
        } while(0); LOCK_GIVE(cptr->hdw->big_lock);
        return ret;
}


/* Retrieve control's minimum value (int type) */
int pvr2_ctrl_get_min(struct pvr2_ctrl *cptr)
{
        int ret = 0;
        if (!cptr) return 0;
        LOCK_TAKE(cptr->hdw->big_lock); do {
                if (cptr->info->get_min_value) {
                        cptr->info->get_min_value(cptr,&ret);
                } else if (cptr->info->type == pvr2_ctl_int) {
                        ret = cptr->info->def.type_int.min_value;
                }
        } while(0); LOCK_GIVE(cptr->hdw->big_lock);
        return ret;
}


/* Retrieve control's default value (any type) */
int pvr2_ctrl_get_def(struct pvr2_ctrl *cptr, int *valptr)
{
        int ret = 0;
        if (!cptr) return -EINVAL;
        LOCK_TAKE(cptr->hdw->big_lock); do {
                if (cptr->info->get_def_value) {
                        ret = cptr->info->get_def_value(cptr, valptr);
                } else {
                        *valptr = cptr->info->default_value;
                }
        } while(0); LOCK_GIVE(cptr->hdw->big_lock);
        return ret;
}


/* Retrieve control's enumeration count (enum only) */
int pvr2_ctrl_get_cnt(struct pvr2_ctrl *cptr)
{
        int ret = 0;
        if (!cptr) return 0;
        LOCK_TAKE(cptr->hdw->big_lock); do {
                if (cptr->info->type == pvr2_ctl_enum) {
                        ret = cptr->info->def.type_enum.count;
                }
        } while(0); LOCK_GIVE(cptr->hdw->big_lock);
        return ret;
}


/* Retrieve control's valid mask bits (bit mask only) */
int pvr2_ctrl_get_mask(struct pvr2_ctrl *cptr)
{
        int ret = 0;
        if (!cptr) return 0;
        LOCK_TAKE(cptr->hdw->big_lock); do {
                if (cptr->info->type == pvr2_ctl_bitmask) {
                        ret = cptr->info->def.type_bitmask.valid_bits;
                }
        } while(0); LOCK_GIVE(cptr->hdw->big_lock);
        return ret;
}


/* Retrieve the control's name */
const char *pvr2_ctrl_get_name(struct pvr2_ctrl *cptr)
{
        if (!cptr) return NULL;
        return cptr->info->name;
}


/* Retrieve the control's desc */
const char *pvr2_ctrl_get_desc(struct pvr2_ctrl *cptr)
{
        if (!cptr) return NULL;
        return cptr->info->desc;
}


/* Retrieve a control enumeration or bit mask value */
int pvr2_ctrl_get_valname(struct pvr2_ctrl *cptr,int val,
                          char *bptr,unsigned int bmax,
                          unsigned int *blen)
{
        int ret = -EINVAL;
        if (!cptr) return 0;
        *blen = 0;
        LOCK_TAKE(cptr->hdw->big_lock); do {
                if (cptr->info->type == pvr2_ctl_enum) {
                        const char * const *names;
                        names = cptr->info->def.type_enum.value_names;
                        if (pvr2_ctrl_range_check(cptr,val) == 0) {
                                if (names[val]) {
                                        *blen = scnprintf(
                                                bptr,bmax,"%s",
                                                names[val]);
                                } else {
                                        *blen = 0;
                                }
                                ret = 0;
                        }
                } else if (cptr->info->type == pvr2_ctl_bitmask) {
                        const char **names;
                        unsigned int idx;
                        int msk;
                        names = cptr->info->def.type_bitmask.bit_names;
                        val &= cptr->info->def.type_bitmask.valid_bits;
                        for (idx = 0, msk = 1; val; idx++, msk <<= 1) {
                                if (val & msk) {
                                        *blen = scnprintf(bptr,bmax,"%s",
                                                          names[idx]);
                                        ret = 0;
                                        break;
                                }
                        }
                }
        } while(0); LOCK_GIVE(cptr->hdw->big_lock);
        return ret;
}


/* Return V4L ID for this control or zero if none */
int pvr2_ctrl_get_v4lid(struct pvr2_ctrl *cptr)
{
        if (!cptr) return 0;
        return cptr->info->v4l_id;
}


unsigned int pvr2_ctrl_get_v4lflags(struct pvr2_ctrl *cptr)
{
        unsigned int flags = 0;

        if (cptr->info->get_v4lflags) {
                flags = cptr->info->get_v4lflags(cptr);
        }

        if (cptr->info->set_value) {
                flags &= ~V4L2_CTRL_FLAG_READ_ONLY;
        } else {
                flags |= V4L2_CTRL_FLAG_READ_ONLY;
        }

        return flags;
}


/* Return true if control is writable */
int pvr2_ctrl_is_writable(struct pvr2_ctrl *cptr)
{
        if (!cptr) return 0;
        return cptr->info->set_value != NULL;
}


/* Return true if control has custom symbolic representation */
int pvr2_ctrl_has_custom_symbols(struct pvr2_ctrl *cptr)
{
        if (!cptr) return 0;
        if (!cptr->info->val_to_sym) return 0;
        if (!cptr->info->sym_to_val) return 0;
        return !0;
}


/* Convert a given mask/val to a custom symbolic value */
int pvr2_ctrl_custom_value_to_sym(struct pvr2_ctrl *cptr,
                                  int mask,int val,
                                  char *buf,unsigned int maxlen,
                                  unsigned int *len)
{
        if (!cptr) return -EINVAL;
        if (!cptr->info->val_to_sym) return -EINVAL;
        return cptr->info->val_to_sym(cptr,mask,val,buf,maxlen,len);
}


/* Convert a symbolic value to a mask/value pair */
int pvr2_ctrl_custom_sym_to_value(struct pvr2_ctrl *cptr,
                                  const char *buf,unsigned int len,
                                  int *maskptr,int *valptr)
{
        if (!cptr) return -EINVAL;
        if (!cptr->info->sym_to_val) return -EINVAL;
        return cptr->info->sym_to_val(cptr,buf,len,maskptr,valptr);
}


static unsigned int gen_bitmask_string(int msk,int val,int msk_only,
                                       const char **names,
                                       char *ptr,unsigned int len)
{
        unsigned int idx;
        long sm,um;
        int spcFl;
        unsigned int uc,cnt;
        const char *idStr;

        spcFl = 0;
        uc = 0;
        um = 0;
        for (idx = 0, sm = 1; msk; idx++, sm <<= 1) {
                if (sm & msk) {
                        msk &= ~sm;
                        idStr = names[idx];
                        if (idStr) {
                                cnt = scnprintf(ptr,len,"%s%s%s",
                                                (spcFl ? " " : ""),
                                                (msk_only ? "" :
                                                 ((val & sm) ? "+" : "-")),
                                                idStr);
                                ptr += cnt; len -= cnt; uc += cnt;
                                spcFl = !0;
                        } else {
                                um |= sm;
                        }
                }
        }
        if (um) {
                if (msk_only) {
                        cnt = scnprintf(ptr,len,"%s0x%lx",
                                        (spcFl ? " " : ""),
                                        um);
                        ptr += cnt; len -= cnt; uc += cnt;
                        spcFl = !0;
                } else if (um & val) {
                        cnt = scnprintf(ptr,len,"%s+0x%lx",
                                        (spcFl ? " " : ""),
                                        um & val);
                        ptr += cnt; len -= cnt; uc += cnt;
                        spcFl = !0;
                } else if (um & ~val) {
                        cnt = scnprintf(ptr,len,"%s+0x%lx",
                                        (spcFl ? " " : ""),
                                        um & ~val);
                        ptr += cnt; len -= cnt; uc += cnt;
                        spcFl = !0;
                }
        }
        return uc;
}


static const char *boolNames[] = {
        "false",
        "true",
        "no",
        "yes",
};


static int parse_token(const char *ptr,unsigned int len,
                       int *valptr,
                       const char * const *names, unsigned int namecnt)
{
        unsigned int slen;
        unsigned int idx;
        *valptr = 0;
        if (!names) namecnt = 0;
        for (idx = 0; idx < namecnt; idx++) {
                if (!names[idx]) continue;
                slen = strlen(names[idx]);
                if (slen != len) continue;
                if (memcmp(names[idx],ptr,slen)) continue;
                *valptr = idx;
                return 0;
        }
        return kstrtoint(ptr, 0, valptr) ? -EINVAL : 1;
}


static int parse_mtoken(const char *ptr,unsigned int len,
                        int *valptr,
                        const char **names,int valid_bits)
{
        unsigned int slen;
        unsigned int idx;
        int msk;
        *valptr = 0;
        for (idx = 0, msk = 1; valid_bits; idx++, msk <<= 1) {
                if (!(msk & valid_bits)) continue;
                valid_bits &= ~msk;
                if (!names[idx]) continue;
                slen = strlen(names[idx]);
                if (slen != len) continue;
                if (memcmp(names[idx],ptr,slen)) continue;
                *valptr = msk;
                return 0;
        }
        return kstrtoint(ptr, 0, valptr);
}


static int parse_tlist(const char *ptr,unsigned int len,
                       int *maskptr,int *valptr,
                       const char **names,int valid_bits)
{
        unsigned int cnt;
        int mask,val,kv,mode,ret;
        mask = 0;
        val = 0;
        ret = 0;
        while (len) {
                cnt = 0;
                while ((cnt < len) &&
                       ((ptr[cnt] <= 32) ||
                        (ptr[cnt] >= 127))) cnt++;
                ptr += cnt;
                len -= cnt;
                mode = 0;
                if ((*ptr == '-') || (*ptr == '+')) {
                        mode = (*ptr == '-') ? -1 : 1;
                        ptr++;
                        len--;
                }
                cnt = 0;
                while (cnt < len) {
                        if (ptr[cnt] <= 32) break;
                        if (ptr[cnt] >= 127) break;
                        cnt++;
                }
                if (!cnt) break;
                if (parse_mtoken(ptr,cnt,&kv,names,valid_bits)) {
                        ret = -EINVAL;
                        break;
                }
                ptr += cnt;
                len -= cnt;
                switch (mode) {
                case 0:
                        mask = valid_bits;
                        val |= kv;
                        break;
                case -1:
                        mask |= kv;
                        val &= ~kv;
                        break;
                case 1:
                        mask |= kv;
                        val |= kv;
                        break;
                default:
                        break;
                }
        }
        *maskptr = mask;
        *valptr = val;
        return ret;
}


/* Convert a symbolic value to a mask/value pair */
int pvr2_ctrl_sym_to_value(struct pvr2_ctrl *cptr,
                           const char *ptr,unsigned int len,
                           int *maskptr,int *valptr)
{
        int ret = -EINVAL;
        unsigned int cnt;

        *maskptr = 0;
        *valptr = 0;

        cnt = 0;
        while ((cnt < len) && ((ptr[cnt] <= 32) || (ptr[cnt] >= 127))) cnt++;
        len -= cnt; ptr += cnt;
        cnt = 0;
        while ((cnt < len) && ((ptr[len-(cnt+1)] <= 32) ||
                               (ptr[len-(cnt+1)] >= 127))) cnt++;
        len -= cnt;

        if (!len) return -EINVAL;

        LOCK_TAKE(cptr->hdw->big_lock); do {
                if (cptr->info->type == pvr2_ctl_int) {
                        ret = parse_token(ptr,len,valptr,NULL,0);
                        if (ret >= 0) {
                                ret = pvr2_ctrl_range_check(cptr,*valptr);
                        }
                        *maskptr = ~0;
                } else if (cptr->info->type == pvr2_ctl_bool) {
                        ret = parse_token(ptr,len,valptr,boolNames,
                                          ARRAY_SIZE(boolNames));
                        if (ret == 1) {
                                *valptr = *valptr ? !0 : 0;
                        } else if (ret == 0) {
                                *valptr = (*valptr & 1) ? !0 : 0;
                        }
                        *maskptr = 1;
                } else if (cptr->info->type == pvr2_ctl_enum) {
                        ret = parse_token(
                                ptr,len,valptr,
                                cptr->info->def.type_enum.value_names,
                                cptr->info->def.type_enum.count);
                        if (ret >= 0) {
                                ret = pvr2_ctrl_range_check(cptr,*valptr);
                        }
                        *maskptr = ~0;
                } else if (cptr->info->type == pvr2_ctl_bitmask) {
                        ret = parse_tlist(
                                ptr,len,maskptr,valptr,
                                cptr->info->def.type_bitmask.bit_names,
                                cptr->info->def.type_bitmask.valid_bits);
                }
        } while(0); LOCK_GIVE(cptr->hdw->big_lock);
        return ret;
}


/* Convert a given mask/val to a symbolic value */
int pvr2_ctrl_value_to_sym_internal(struct pvr2_ctrl *cptr,
                                    int mask,int val,
                                    char *buf,unsigned int maxlen,
                                    unsigned int *len)
{
        int ret = -EINVAL;

        *len = 0;
        if (cptr->info->type == pvr2_ctl_int) {
                *len = scnprintf(buf,maxlen,"%d",val);
                ret = 0;
        } else if (cptr->info->type == pvr2_ctl_bool) {
                *len = scnprintf(buf,maxlen,"%s",val ? "true" : "false");
                ret = 0;
        } else if (cptr->info->type == pvr2_ctl_enum) {
                const char * const *names;
                names = cptr->info->def.type_enum.value_names;
                if ((val >= 0) &&
                    (val < cptr->info->def.type_enum.count)) {
                        if (names[val]) {
                                *len = scnprintf(
                                        buf,maxlen,"%s",
                                        names[val]);
                        } else {
                                *len = 0;
                        }
                        ret = 0;
                }
        } else if (cptr->info->type == pvr2_ctl_bitmask) {
                *len = gen_bitmask_string(
                        val & mask & cptr->info->def.type_bitmask.valid_bits,
                        ~0,!0,
                        cptr->info->def.type_bitmask.bit_names,
                        buf,maxlen);
        }
        return ret;
}


/* Convert a given mask/val to a symbolic value */
int pvr2_ctrl_value_to_sym(struct pvr2_ctrl *cptr,
                           int mask,int val,
                           char *buf,unsigned int maxlen,
                           unsigned int *len)
{
        int ret;
        LOCK_TAKE(cptr->hdw->big_lock); do {
                ret = pvr2_ctrl_value_to_sym_internal(cptr,mask,val,
                                                      buf,maxlen,len);
        } while(0); LOCK_GIVE(cptr->hdw->big_lock);
        return ret;
}