linux/sound/pci/ctxfi/ctamixer.c
Takashi Iwai 76c4718322 ALSA: ctxfi: Fix out-of-range access
The master and next_conj of rcs_ops are used for iterating the
resource list entries, and currently those are supposed to return the
current value.  The problem is that next_conf may go over the last
entry before the loop abort condition is evaluated, and it may return
the "current" value that is beyond the array size.  It was caught
recently as a GPF, for example.

Those return values are, however, never actually evaluated, hence
basically we don't have to consider the current value as the return at
all.  By dropping those return values, the potential out-of-range
access above is also fixed automatically.

This patch changes the return type of master and next_conj callbacks
to void and drop the superfluous code accordingly.

BugLink: https://bugzilla.kernel.org/show_bug.cgi?id=214985
Cc: <stable@vger.kernel.org>
Link: https://lore.kernel.org/r/20211118215729.26257-1-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2021-11-18 22:57:55 +01:00

485 lines
9.5 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
*
* @File ctamixer.c
*
* @Brief
* This file contains the implementation of the Audio Mixer
* resource management object.
*
* @Author Liu Chun
* @Date May 21 2008
*/
#include "ctamixer.h"
#include "cthardware.h"
#include <linux/slab.h>
#define AMIXER_RESOURCE_NUM 256
#define SUM_RESOURCE_NUM 256
#define AMIXER_Y_IMMEDIATE 1
#define BLANK_SLOT 4094
static void amixer_master(struct rsc *rsc)
{
rsc->conj = 0;
rsc->idx = container_of(rsc, struct amixer, rsc)->idx[0];
}
static void amixer_next_conj(struct rsc *rsc)
{
rsc->conj++;
}
static int amixer_index(const struct rsc *rsc)
{
return container_of(rsc, struct amixer, rsc)->idx[rsc->conj];
}
static int amixer_output_slot(const struct rsc *rsc)
{
return (amixer_index(rsc) << 4) + 0x4;
}
static const struct rsc_ops amixer_basic_rsc_ops = {
.master = amixer_master,
.next_conj = amixer_next_conj,
.index = amixer_index,
.output_slot = amixer_output_slot,
};
static int amixer_set_input(struct amixer *amixer, struct rsc *rsc)
{
struct hw *hw;
hw = amixer->rsc.hw;
hw->amixer_set_mode(amixer->rsc.ctrl_blk, AMIXER_Y_IMMEDIATE);
amixer->input = rsc;
if (!rsc)
hw->amixer_set_x(amixer->rsc.ctrl_blk, BLANK_SLOT);
else
hw->amixer_set_x(amixer->rsc.ctrl_blk,
rsc->ops->output_slot(rsc));
return 0;
}
/* y is a 14-bit immediate constant */
static int amixer_set_y(struct amixer *amixer, unsigned int y)
{
struct hw *hw;
hw = amixer->rsc.hw;
hw->amixer_set_y(amixer->rsc.ctrl_blk, y);
return 0;
}
static int amixer_set_invalid_squash(struct amixer *amixer, unsigned int iv)
{
struct hw *hw;
hw = amixer->rsc.hw;
hw->amixer_set_iv(amixer->rsc.ctrl_blk, iv);
return 0;
}
static int amixer_set_sum(struct amixer *amixer, struct sum *sum)
{
struct hw *hw;
hw = amixer->rsc.hw;
amixer->sum = sum;
if (!sum) {
hw->amixer_set_se(amixer->rsc.ctrl_blk, 0);
} else {
hw->amixer_set_se(amixer->rsc.ctrl_blk, 1);
hw->amixer_set_sadr(amixer->rsc.ctrl_blk,
sum->rsc.ops->index(&sum->rsc));
}
return 0;
}
static int amixer_commit_write(struct amixer *amixer)
{
struct hw *hw;
unsigned int index;
int i;
struct rsc *input;
struct sum *sum;
hw = amixer->rsc.hw;
input = amixer->input;
sum = amixer->sum;
/* Program master and conjugate resources */
amixer->rsc.ops->master(&amixer->rsc);
if (input)
input->ops->master(input);
if (sum)
sum->rsc.ops->master(&sum->rsc);
for (i = 0; i < amixer->rsc.msr; i++) {
hw->amixer_set_dirty_all(amixer->rsc.ctrl_blk);
if (input) {
hw->amixer_set_x(amixer->rsc.ctrl_blk,
input->ops->output_slot(input));
input->ops->next_conj(input);
}
if (sum) {
hw->amixer_set_sadr(amixer->rsc.ctrl_blk,
sum->rsc.ops->index(&sum->rsc));
sum->rsc.ops->next_conj(&sum->rsc);
}
index = amixer->rsc.ops->output_slot(&amixer->rsc);
hw->amixer_commit_write(hw, index, amixer->rsc.ctrl_blk);
amixer->rsc.ops->next_conj(&amixer->rsc);
}
amixer->rsc.ops->master(&amixer->rsc);
if (input)
input->ops->master(input);
if (sum)
sum->rsc.ops->master(&sum->rsc);
return 0;
}
static int amixer_commit_raw_write(struct amixer *amixer)
{
struct hw *hw;
unsigned int index;
hw = amixer->rsc.hw;
index = amixer->rsc.ops->output_slot(&amixer->rsc);
hw->amixer_commit_write(hw, index, amixer->rsc.ctrl_blk);
return 0;
}
static int amixer_get_y(struct amixer *amixer)
{
struct hw *hw;
hw = amixer->rsc.hw;
return hw->amixer_get_y(amixer->rsc.ctrl_blk);
}
static int amixer_setup(struct amixer *amixer, struct rsc *input,
unsigned int scale, struct sum *sum)
{
amixer_set_input(amixer, input);
amixer_set_y(amixer, scale);
amixer_set_sum(amixer, sum);
amixer_commit_write(amixer);
return 0;
}
static const struct amixer_rsc_ops amixer_ops = {
.set_input = amixer_set_input,
.set_invalid_squash = amixer_set_invalid_squash,
.set_scale = amixer_set_y,
.set_sum = amixer_set_sum,
.commit_write = amixer_commit_write,
.commit_raw_write = amixer_commit_raw_write,
.setup = amixer_setup,
.get_scale = amixer_get_y,
};
static int amixer_rsc_init(struct amixer *amixer,
const struct amixer_desc *desc,
struct amixer_mgr *mgr)
{
int err;
err = rsc_init(&amixer->rsc, amixer->idx[0],
AMIXER, desc->msr, mgr->mgr.hw);
if (err)
return err;
/* Set amixer specific operations */
amixer->rsc.ops = &amixer_basic_rsc_ops;
amixer->ops = &amixer_ops;
amixer->input = NULL;
amixer->sum = NULL;
amixer_setup(amixer, NULL, 0, NULL);
return 0;
}
static int amixer_rsc_uninit(struct amixer *amixer)
{
amixer_setup(amixer, NULL, 0, NULL);
rsc_uninit(&amixer->rsc);
amixer->ops = NULL;
amixer->input = NULL;
amixer->sum = NULL;
return 0;
}
static int get_amixer_rsc(struct amixer_mgr *mgr,
const struct amixer_desc *desc,
struct amixer **ramixer)
{
int err, i;
unsigned int idx;
struct amixer *amixer;
unsigned long flags;
*ramixer = NULL;
/* Allocate mem for amixer resource */
amixer = kzalloc(sizeof(*amixer), GFP_KERNEL);
if (!amixer)
return -ENOMEM;
/* Check whether there are sufficient
* amixer resources to meet request. */
err = 0;
spin_lock_irqsave(&mgr->mgr_lock, flags);
for (i = 0; i < desc->msr; i++) {
err = mgr_get_resource(&mgr->mgr, 1, &idx);
if (err)
break;
amixer->idx[i] = idx;
}
spin_unlock_irqrestore(&mgr->mgr_lock, flags);
if (err) {
dev_err(mgr->card->dev,
"Can't meet AMIXER resource request!\n");
goto error;
}
err = amixer_rsc_init(amixer, desc, mgr);
if (err)
goto error;
*ramixer = amixer;
return 0;
error:
spin_lock_irqsave(&mgr->mgr_lock, flags);
for (i--; i >= 0; i--)
mgr_put_resource(&mgr->mgr, 1, amixer->idx[i]);
spin_unlock_irqrestore(&mgr->mgr_lock, flags);
kfree(amixer);
return err;
}
static int put_amixer_rsc(struct amixer_mgr *mgr, struct amixer *amixer)
{
unsigned long flags;
int i;
spin_lock_irqsave(&mgr->mgr_lock, flags);
for (i = 0; i < amixer->rsc.msr; i++)
mgr_put_resource(&mgr->mgr, 1, amixer->idx[i]);
spin_unlock_irqrestore(&mgr->mgr_lock, flags);
amixer_rsc_uninit(amixer);
kfree(amixer);
return 0;
}
int amixer_mgr_create(struct hw *hw, struct amixer_mgr **ramixer_mgr)
{
int err;
struct amixer_mgr *amixer_mgr;
*ramixer_mgr = NULL;
amixer_mgr = kzalloc(sizeof(*amixer_mgr), GFP_KERNEL);
if (!amixer_mgr)
return -ENOMEM;
err = rsc_mgr_init(&amixer_mgr->mgr, AMIXER, AMIXER_RESOURCE_NUM, hw);
if (err)
goto error;
spin_lock_init(&amixer_mgr->mgr_lock);
amixer_mgr->get_amixer = get_amixer_rsc;
amixer_mgr->put_amixer = put_amixer_rsc;
amixer_mgr->card = hw->card;
*ramixer_mgr = amixer_mgr;
return 0;
error:
kfree(amixer_mgr);
return err;
}
int amixer_mgr_destroy(struct amixer_mgr *amixer_mgr)
{
rsc_mgr_uninit(&amixer_mgr->mgr);
kfree(amixer_mgr);
return 0;
}
/* SUM resource management */
static void sum_master(struct rsc *rsc)
{
rsc->conj = 0;
rsc->idx = container_of(rsc, struct sum, rsc)->idx[0];
}
static void sum_next_conj(struct rsc *rsc)
{
rsc->conj++;
}
static int sum_index(const struct rsc *rsc)
{
return container_of(rsc, struct sum, rsc)->idx[rsc->conj];
}
static int sum_output_slot(const struct rsc *rsc)
{
return (sum_index(rsc) << 4) + 0xc;
}
static const struct rsc_ops sum_basic_rsc_ops = {
.master = sum_master,
.next_conj = sum_next_conj,
.index = sum_index,
.output_slot = sum_output_slot,
};
static int sum_rsc_init(struct sum *sum,
const struct sum_desc *desc,
struct sum_mgr *mgr)
{
int err;
err = rsc_init(&sum->rsc, sum->idx[0], SUM, desc->msr, mgr->mgr.hw);
if (err)
return err;
sum->rsc.ops = &sum_basic_rsc_ops;
return 0;
}
static int sum_rsc_uninit(struct sum *sum)
{
rsc_uninit(&sum->rsc);
return 0;
}
static int get_sum_rsc(struct sum_mgr *mgr,
const struct sum_desc *desc,
struct sum **rsum)
{
int err, i;
unsigned int idx;
struct sum *sum;
unsigned long flags;
*rsum = NULL;
/* Allocate mem for sum resource */
sum = kzalloc(sizeof(*sum), GFP_KERNEL);
if (!sum)
return -ENOMEM;
/* Check whether there are sufficient sum resources to meet request. */
err = 0;
spin_lock_irqsave(&mgr->mgr_lock, flags);
for (i = 0; i < desc->msr; i++) {
err = mgr_get_resource(&mgr->mgr, 1, &idx);
if (err)
break;
sum->idx[i] = idx;
}
spin_unlock_irqrestore(&mgr->mgr_lock, flags);
if (err) {
dev_err(mgr->card->dev,
"Can't meet SUM resource request!\n");
goto error;
}
err = sum_rsc_init(sum, desc, mgr);
if (err)
goto error;
*rsum = sum;
return 0;
error:
spin_lock_irqsave(&mgr->mgr_lock, flags);
for (i--; i >= 0; i--)
mgr_put_resource(&mgr->mgr, 1, sum->idx[i]);
spin_unlock_irqrestore(&mgr->mgr_lock, flags);
kfree(sum);
return err;
}
static int put_sum_rsc(struct sum_mgr *mgr, struct sum *sum)
{
unsigned long flags;
int i;
spin_lock_irqsave(&mgr->mgr_lock, flags);
for (i = 0; i < sum->rsc.msr; i++)
mgr_put_resource(&mgr->mgr, 1, sum->idx[i]);
spin_unlock_irqrestore(&mgr->mgr_lock, flags);
sum_rsc_uninit(sum);
kfree(sum);
return 0;
}
int sum_mgr_create(struct hw *hw, struct sum_mgr **rsum_mgr)
{
int err;
struct sum_mgr *sum_mgr;
*rsum_mgr = NULL;
sum_mgr = kzalloc(sizeof(*sum_mgr), GFP_KERNEL);
if (!sum_mgr)
return -ENOMEM;
err = rsc_mgr_init(&sum_mgr->mgr, SUM, SUM_RESOURCE_NUM, hw);
if (err)
goto error;
spin_lock_init(&sum_mgr->mgr_lock);
sum_mgr->get_sum = get_sum_rsc;
sum_mgr->put_sum = put_sum_rsc;
sum_mgr->card = hw->card;
*rsum_mgr = sum_mgr;
return 0;
error:
kfree(sum_mgr);
return err;
}
int sum_mgr_destroy(struct sum_mgr *sum_mgr)
{
rsc_mgr_uninit(&sum_mgr->mgr);
kfree(sum_mgr);
return 0;
}