ALSA: hda: hdac_ext_stream: fix potential locking issues

The code for hdac_ext_stream seems inherited from hdac_stream, and
similar locking issues are present: the use of the bus->reg_lock
spinlock is inconsistent, with only writes to specific fields being
protected.

Apply similar fix as in hdac_stream by protecting all accesses to
'link_locked' and 'decoupled' fields, with a new helper
snd_hdac_ext_stream_decouple_locked() added to simplify code
changes.

Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Link: https://lore.kernel.org/r/20210924192417.169243-4-pierre-louis.bossart@linux.intel.com
Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
Pierre-Louis Bossart 2021-09-24 14:24:16 -05:00 коммит произвёл Takashi Iwai
Родитель 1465d06a6d
Коммит 868ddfcef3
2 изменённых файлов: 30 добавлений и 18 удалений

Просмотреть файл

@ -88,6 +88,8 @@ struct hdac_ext_stream *snd_hdac_ext_stream_assign(struct hdac_bus *bus,
struct snd_pcm_substream *substream, struct snd_pcm_substream *substream,
int type); int type);
void snd_hdac_ext_stream_release(struct hdac_ext_stream *azx_dev, int type); void snd_hdac_ext_stream_release(struct hdac_ext_stream *azx_dev, int type);
void snd_hdac_ext_stream_decouple_locked(struct hdac_bus *bus,
struct hdac_ext_stream *azx_dev, bool decouple);
void snd_hdac_ext_stream_decouple(struct hdac_bus *bus, void snd_hdac_ext_stream_decouple(struct hdac_bus *bus,
struct hdac_ext_stream *azx_dev, bool decouple); struct hdac_ext_stream *azx_dev, bool decouple);
void snd_hdac_ext_stop_streams(struct hdac_bus *bus); void snd_hdac_ext_stop_streams(struct hdac_bus *bus);

Просмотреть файл

@ -106,6 +106,25 @@ void snd_hdac_stream_free_all(struct hdac_bus *bus)
} }
EXPORT_SYMBOL_GPL(snd_hdac_stream_free_all); EXPORT_SYMBOL_GPL(snd_hdac_stream_free_all);
void snd_hdac_ext_stream_decouple_locked(struct hdac_bus *bus,
struct hdac_ext_stream *stream,
bool decouple)
{
struct hdac_stream *hstream = &stream->hstream;
u32 val;
int mask = AZX_PPCTL_PROCEN(hstream->index);
val = readw(bus->ppcap + AZX_REG_PP_PPCTL) & mask;
if (decouple && !val)
snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, mask, mask);
else if (!decouple && val)
snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, mask, 0);
stream->decoupled = decouple;
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple_locked);
/** /**
* snd_hdac_ext_stream_decouple - decouple the hdac stream * snd_hdac_ext_stream_decouple - decouple the hdac stream
* @bus: HD-audio core bus * @bus: HD-audio core bus
@ -115,19 +134,8 @@ EXPORT_SYMBOL_GPL(snd_hdac_stream_free_all);
void snd_hdac_ext_stream_decouple(struct hdac_bus *bus, void snd_hdac_ext_stream_decouple(struct hdac_bus *bus,
struct hdac_ext_stream *stream, bool decouple) struct hdac_ext_stream *stream, bool decouple)
{ {
struct hdac_stream *hstream = &stream->hstream;
u32 val;
int mask = AZX_PPCTL_PROCEN(hstream->index);
spin_lock_irq(&bus->reg_lock); spin_lock_irq(&bus->reg_lock);
val = readw(bus->ppcap + AZX_REG_PP_PPCTL) & mask; snd_hdac_ext_stream_decouple_locked(bus, stream, decouple);
if (decouple && !val)
snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, mask, mask);
else if (!decouple && val)
snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, mask, 0);
stream->decoupled = decouple;
spin_unlock_irq(&bus->reg_lock); spin_unlock_irq(&bus->reg_lock);
} }
EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple); EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple);
@ -252,6 +260,7 @@ hdac_ext_link_stream_assign(struct hdac_bus *bus,
return NULL; return NULL;
} }
spin_lock_irq(&bus->reg_lock);
list_for_each_entry(stream, &bus->stream_list, list) { list_for_each_entry(stream, &bus->stream_list, list) {
struct hdac_ext_stream *hstream = container_of(stream, struct hdac_ext_stream *hstream = container_of(stream,
struct hdac_ext_stream, struct hdac_ext_stream,
@ -266,17 +275,16 @@ hdac_ext_link_stream_assign(struct hdac_bus *bus,
} }
if (!hstream->link_locked) { if (!hstream->link_locked) {
snd_hdac_ext_stream_decouple(bus, hstream, true); snd_hdac_ext_stream_decouple_locked(bus, hstream, true);
res = hstream; res = hstream;
break; break;
} }
} }
if (res) { if (res) {
spin_lock_irq(&bus->reg_lock);
res->link_locked = 1; res->link_locked = 1;
res->link_substream = substream; res->link_substream = substream;
spin_unlock_irq(&bus->reg_lock);
} }
spin_unlock_irq(&bus->reg_lock);
return res; return res;
} }
@ -292,6 +300,7 @@ hdac_ext_host_stream_assign(struct hdac_bus *bus,
return NULL; return NULL;
} }
spin_lock_irq(&bus->reg_lock);
list_for_each_entry(stream, &bus->stream_list, list) { list_for_each_entry(stream, &bus->stream_list, list) {
struct hdac_ext_stream *hstream = container_of(stream, struct hdac_ext_stream *hstream = container_of(stream,
struct hdac_ext_stream, struct hdac_ext_stream,
@ -301,18 +310,17 @@ hdac_ext_host_stream_assign(struct hdac_bus *bus,
if (!stream->opened) { if (!stream->opened) {
if (!hstream->decoupled) if (!hstream->decoupled)
snd_hdac_ext_stream_decouple(bus, hstream, true); snd_hdac_ext_stream_decouple_locked(bus, hstream, true);
res = hstream; res = hstream;
break; break;
} }
} }
if (res) { if (res) {
spin_lock_irq(&bus->reg_lock);
res->hstream.opened = 1; res->hstream.opened = 1;
res->hstream.running = 0; res->hstream.running = 0;
res->hstream.substream = substream; res->hstream.substream = substream;
spin_unlock_irq(&bus->reg_lock);
} }
spin_unlock_irq(&bus->reg_lock);
return res; return res;
} }
@ -378,15 +386,17 @@ void snd_hdac_ext_stream_release(struct hdac_ext_stream *stream, int type)
break; break;
case HDAC_EXT_STREAM_TYPE_HOST: case HDAC_EXT_STREAM_TYPE_HOST:
spin_lock_irq(&bus->reg_lock);
if (stream->decoupled && !stream->link_locked) if (stream->decoupled && !stream->link_locked)
snd_hdac_ext_stream_decouple(bus, stream, false); snd_hdac_ext_stream_decouple_locked(bus, stream, false);
spin_unlock_irq(&bus->reg_lock);
snd_hdac_stream_release(&stream->hstream); snd_hdac_stream_release(&stream->hstream);
break; break;
case HDAC_EXT_STREAM_TYPE_LINK: case HDAC_EXT_STREAM_TYPE_LINK:
if (stream->decoupled && !stream->hstream.opened)
snd_hdac_ext_stream_decouple(bus, stream, false);
spin_lock_irq(&bus->reg_lock); spin_lock_irq(&bus->reg_lock);
if (stream->decoupled && !stream->hstream.opened)
snd_hdac_ext_stream_decouple_locked(bus, stream, false);
stream->link_locked = 0; stream->link_locked = 0;
stream->link_substream = NULL; stream->link_substream = NULL;
spin_unlock_irq(&bus->reg_lock); spin_unlock_irq(&bus->reg_lock);