cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

fireworks_stream.c (9053B)


      1// SPDX-License-Identifier: GPL-2.0-only
      2/*
      3 * fireworks_stream.c - a part of driver for Fireworks based devices
      4 *
      5 * Copyright (c) 2013-2014 Takashi Sakamoto
      6 */
      7#include "./fireworks.h"
      8
      9#define READY_TIMEOUT_MS	1000
     10
     11static int init_stream(struct snd_efw *efw, struct amdtp_stream *stream)
     12{
     13	struct cmp_connection *conn;
     14	enum cmp_direction c_dir;
     15	enum amdtp_stream_direction s_dir;
     16	int err;
     17
     18	if (stream == &efw->tx_stream) {
     19		conn = &efw->out_conn;
     20		c_dir = CMP_OUTPUT;
     21		s_dir = AMDTP_IN_STREAM;
     22	} else {
     23		conn = &efw->in_conn;
     24		c_dir = CMP_INPUT;
     25		s_dir = AMDTP_OUT_STREAM;
     26	}
     27
     28	err = cmp_connection_init(conn, efw->unit, c_dir, 0);
     29	if (err < 0)
     30		return err;
     31
     32	err = amdtp_am824_init(stream, efw->unit, s_dir, CIP_BLOCKING | CIP_UNAWARE_SYT);
     33	if (err < 0) {
     34		amdtp_stream_destroy(stream);
     35		cmp_connection_destroy(conn);
     36		return err;
     37	}
     38
     39	if (stream == &efw->tx_stream) {
     40		// Fireworks transmits NODATA packets with TAG0.
     41		efw->tx_stream.flags |= CIP_EMPTY_WITH_TAG0;
     42		// Fireworks has its own meaning for dbc.
     43		efw->tx_stream.flags |= CIP_DBC_IS_END_EVENT;
     44		// Fireworks reset dbc at bus reset.
     45		efw->tx_stream.flags |= CIP_SKIP_DBC_ZERO_CHECK;
     46		// But Recent firmwares starts packets with non-zero dbc.
     47		// Driver version 5.7.6 installs firmware version 5.7.3.
     48		if (efw->is_fireworks3 &&
     49		    (efw->firmware_version == 0x5070000 ||
     50		     efw->firmware_version == 0x5070300 ||
     51		     efw->firmware_version == 0x5080000))
     52			efw->tx_stream.flags |= CIP_UNALIGHED_DBC;
     53		// AudioFire9 always reports wrong dbs. Onyx 1200F with the latest firmware (v4.6.0)
     54		// also report wrong dbs at 88.2 kHz or greater.
     55		if (efw->is_af9 || efw->firmware_version == 0x4060000)
     56			efw->tx_stream.flags |= CIP_WRONG_DBS;
     57		// Firmware version 5.5 reports fixed interval for dbc.
     58		if (efw->firmware_version == 0x5050000)
     59			efw->tx_stream.ctx_data.tx.dbc_interval = 8;
     60	}
     61
     62	return err;
     63}
     64
     65static int start_stream(struct snd_efw *efw, struct amdtp_stream *stream,
     66			unsigned int rate)
     67{
     68	struct cmp_connection *conn;
     69	int err;
     70
     71	if (stream == &efw->tx_stream)
     72		conn = &efw->out_conn;
     73	else
     74		conn = &efw->in_conn;
     75
     76	// Establish connection via CMP.
     77	err = cmp_connection_establish(conn);
     78	if (err < 0)
     79		return err;
     80
     81	// Start amdtp stream.
     82	err = amdtp_domain_add_stream(&efw->domain, stream,
     83				      conn->resources.channel, conn->speed);
     84	if (err < 0) {
     85		cmp_connection_break(conn);
     86		return err;
     87	}
     88
     89	return 0;
     90}
     91
     92// This function should be called before starting the stream or after stopping
     93// the streams.
     94static void destroy_stream(struct snd_efw *efw, struct amdtp_stream *stream)
     95{
     96	amdtp_stream_destroy(stream);
     97
     98	if (stream == &efw->tx_stream)
     99		cmp_connection_destroy(&efw->out_conn);
    100	else
    101		cmp_connection_destroy(&efw->in_conn);
    102}
    103
    104static int
    105check_connection_used_by_others(struct snd_efw *efw, struct amdtp_stream *s)
    106{
    107	struct cmp_connection *conn;
    108	bool used;
    109	int err;
    110
    111	if (s == &efw->tx_stream)
    112		conn = &efw->out_conn;
    113	else
    114		conn = &efw->in_conn;
    115
    116	err = cmp_connection_check_used(conn, &used);
    117	if ((err >= 0) && used && !amdtp_stream_running(s)) {
    118		dev_err(&efw->unit->device,
    119			"Connection established by others: %cPCR[%d]\n",
    120			(conn->direction == CMP_OUTPUT) ? 'o' : 'i',
    121			conn->pcr_index);
    122		err = -EBUSY;
    123	}
    124
    125	return err;
    126}
    127
    128int snd_efw_stream_init_duplex(struct snd_efw *efw)
    129{
    130	int err;
    131
    132	err = init_stream(efw, &efw->tx_stream);
    133	if (err < 0)
    134		return err;
    135
    136	err = init_stream(efw, &efw->rx_stream);
    137	if (err < 0) {
    138		destroy_stream(efw, &efw->tx_stream);
    139		return err;
    140	}
    141
    142	err = amdtp_domain_init(&efw->domain);
    143	if (err < 0) {
    144		destroy_stream(efw, &efw->tx_stream);
    145		destroy_stream(efw, &efw->rx_stream);
    146		return err;
    147	}
    148
    149	// set IEC61883 compliant mode (actually not fully compliant...).
    150	err = snd_efw_command_set_tx_mode(efw, SND_EFW_TRANSPORT_MODE_IEC61883);
    151	if (err < 0) {
    152		destroy_stream(efw, &efw->tx_stream);
    153		destroy_stream(efw, &efw->rx_stream);
    154	}
    155
    156	return err;
    157}
    158
    159static int keep_resources(struct snd_efw *efw, struct amdtp_stream *stream,
    160			  unsigned int rate, unsigned int mode)
    161{
    162	unsigned int pcm_channels;
    163	unsigned int midi_ports;
    164	struct cmp_connection *conn;
    165	int err;
    166
    167	if (stream == &efw->tx_stream) {
    168		pcm_channels = efw->pcm_capture_channels[mode];
    169		midi_ports = efw->midi_out_ports;
    170		conn = &efw->out_conn;
    171	} else {
    172		pcm_channels = efw->pcm_playback_channels[mode];
    173		midi_ports = efw->midi_in_ports;
    174		conn = &efw->in_conn;
    175	}
    176
    177	err = amdtp_am824_set_parameters(stream, rate, pcm_channels,
    178					 midi_ports, false);
    179	if (err < 0)
    180		return err;
    181
    182	return cmp_connection_reserve(conn, amdtp_stream_get_max_payload(stream));
    183}
    184
    185int snd_efw_stream_reserve_duplex(struct snd_efw *efw, unsigned int rate,
    186				  unsigned int frames_per_period,
    187				  unsigned int frames_per_buffer)
    188{
    189	unsigned int curr_rate;
    190	int err;
    191
    192	// Considering JACK/FFADO streaming:
    193	// TODO: This can be removed hwdep functionality becomes popular.
    194	err = check_connection_used_by_others(efw, &efw->rx_stream);
    195	if (err < 0)
    196		return err;
    197
    198	// stop streams if rate is different.
    199	err = snd_efw_command_get_sampling_rate(efw, &curr_rate);
    200	if (err < 0)
    201		return err;
    202	if (rate == 0)
    203		rate = curr_rate;
    204	if (rate != curr_rate) {
    205		amdtp_domain_stop(&efw->domain);
    206
    207		cmp_connection_break(&efw->out_conn);
    208		cmp_connection_break(&efw->in_conn);
    209
    210		cmp_connection_release(&efw->out_conn);
    211		cmp_connection_release(&efw->in_conn);
    212	}
    213
    214	if (efw->substreams_counter == 0 || rate != curr_rate) {
    215		unsigned int mode;
    216
    217		err = snd_efw_command_set_sampling_rate(efw, rate);
    218		if (err < 0)
    219			return err;
    220
    221		err = snd_efw_get_multiplier_mode(rate, &mode);
    222		if (err < 0)
    223			return err;
    224
    225		err = keep_resources(efw, &efw->tx_stream, rate, mode);
    226		if (err < 0)
    227			return err;
    228
    229		err = keep_resources(efw, &efw->rx_stream, rate, mode);
    230		if (err < 0) {
    231			cmp_connection_release(&efw->in_conn);
    232			return err;
    233		}
    234
    235		err = amdtp_domain_set_events_per_period(&efw->domain,
    236					frames_per_period, frames_per_buffer);
    237		if (err < 0) {
    238			cmp_connection_release(&efw->in_conn);
    239			cmp_connection_release(&efw->out_conn);
    240			return err;
    241		}
    242	}
    243
    244	return 0;
    245}
    246
    247int snd_efw_stream_start_duplex(struct snd_efw *efw)
    248{
    249	unsigned int rate;
    250	int err = 0;
    251
    252	// Need no substreams.
    253	if (efw->substreams_counter == 0)
    254		return -EIO;
    255
    256	if (amdtp_streaming_error(&efw->rx_stream) ||
    257	    amdtp_streaming_error(&efw->tx_stream)) {
    258		amdtp_domain_stop(&efw->domain);
    259		cmp_connection_break(&efw->out_conn);
    260		cmp_connection_break(&efw->in_conn);
    261	}
    262
    263	err = snd_efw_command_get_sampling_rate(efw, &rate);
    264	if (err < 0)
    265		return err;
    266
    267	if (!amdtp_stream_running(&efw->rx_stream)) {
    268		unsigned int tx_init_skip_cycles;
    269
    270		// Audiofire 2/4 skip an isochronous cycle several thousands after starting
    271		// packet transmission.
    272		if (efw->is_fireworks3 && !efw->is_af9)
    273			tx_init_skip_cycles = 6000;
    274		else
    275			tx_init_skip_cycles = 0;
    276
    277		err = start_stream(efw, &efw->rx_stream, rate);
    278		if (err < 0)
    279			goto error;
    280
    281		err = start_stream(efw, &efw->tx_stream, rate);
    282		if (err < 0)
    283			goto error;
    284
    285		// NOTE: The device ignores presentation time expressed by the value of syt field
    286		// of CIP header in received packets. The sequence of the number of data blocks per
    287		// packet is important for media clock recovery.
    288		err = amdtp_domain_start(&efw->domain, tx_init_skip_cycles, true, false);
    289		if (err < 0)
    290			goto error;
    291
    292		if (!amdtp_domain_wait_ready(&efw->domain, READY_TIMEOUT_MS)) {
    293			err = -ETIMEDOUT;
    294			goto error;
    295		}
    296	}
    297
    298	return 0;
    299error:
    300	amdtp_domain_stop(&efw->domain);
    301
    302	cmp_connection_break(&efw->out_conn);
    303	cmp_connection_break(&efw->in_conn);
    304
    305	return err;
    306}
    307
    308void snd_efw_stream_stop_duplex(struct snd_efw *efw)
    309{
    310	if (efw->substreams_counter == 0) {
    311		amdtp_domain_stop(&efw->domain);
    312
    313		cmp_connection_break(&efw->out_conn);
    314		cmp_connection_break(&efw->in_conn);
    315
    316		cmp_connection_release(&efw->out_conn);
    317		cmp_connection_release(&efw->in_conn);
    318	}
    319}
    320
    321void snd_efw_stream_update_duplex(struct snd_efw *efw)
    322{
    323	amdtp_domain_stop(&efw->domain);
    324
    325	cmp_connection_break(&efw->out_conn);
    326	cmp_connection_break(&efw->in_conn);
    327
    328	amdtp_stream_pcm_abort(&efw->rx_stream);
    329	amdtp_stream_pcm_abort(&efw->tx_stream);
    330}
    331
    332void snd_efw_stream_destroy_duplex(struct snd_efw *efw)
    333{
    334	amdtp_domain_destroy(&efw->domain);
    335
    336	destroy_stream(efw, &efw->rx_stream);
    337	destroy_stream(efw, &efw->tx_stream);
    338}
    339
    340void snd_efw_stream_lock_changed(struct snd_efw *efw)
    341{
    342	efw->dev_lock_changed = true;
    343	wake_up(&efw->hwdep_wait);
    344}
    345
    346int snd_efw_stream_lock_try(struct snd_efw *efw)
    347{
    348	int err;
    349
    350	spin_lock_irq(&efw->lock);
    351
    352	/* user land lock this */
    353	if (efw->dev_lock_count < 0) {
    354		err = -EBUSY;
    355		goto end;
    356	}
    357
    358	/* this is the first time */
    359	if (efw->dev_lock_count++ == 0)
    360		snd_efw_stream_lock_changed(efw);
    361	err = 0;
    362end:
    363	spin_unlock_irq(&efw->lock);
    364	return err;
    365}
    366
    367void snd_efw_stream_lock_release(struct snd_efw *efw)
    368{
    369	spin_lock_irq(&efw->lock);
    370
    371	if (WARN_ON(efw->dev_lock_count <= 0))
    372		goto end;
    373	if (--efw->dev_lock_count == 0)
    374		snd_efw_stream_lock_changed(efw);
    375end:
    376	spin_unlock_irq(&efw->lock);
    377}