pcm_lib.c 67.1 KB
Newer Older
2001
2002
2003
			       snd_pcm_uframes_t off,
			       snd_pcm_uframes_t frames,
			       pcm_transfer_f transfer)
Takashi Iwai's avatar
Takashi Iwai committed
2004
2005
2006
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	int channels = runtime->channels;
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
	void **bufs = data;
	int c, err;

	/* convert to bytes; note that it's not frames_to_bytes() here.
	 * in non-interleaved mode, we copy for each channel, thus
	 * each copy is n_samples bytes x channels = whole frames.
	 */
	off = samples_to_bytes(runtime, off);
	frames = samples_to_bytes(runtime, frames);
	hwoff = samples_to_bytes(runtime, hwoff);
	for (c = 0; c < channels; ++c, ++bufs) {
		if (!data || !*bufs)
			err = fill_silence(substream, c, hwoff, NULL, frames);
		else
			err = transfer(substream, c, hwoff, *bufs + off,
				       frames);
		if (err < 0)
			return err;
Linus Torvalds's avatar
Linus Torvalds committed
2025
	}
Takashi Iwai's avatar
Takashi Iwai committed
2026
2027
2028
	return 0;
}

2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
/* fill silence on the given buffer position;
 * called from snd_pcm_playback_silence()
 */
static int fill_silence_frames(struct snd_pcm_substream *substream,
			       snd_pcm_uframes_t off, snd_pcm_uframes_t frames)
{
	if (substream->runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED ||
	    substream->runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED)
		return interleaved_copy(substream, off, NULL, 0, frames,
					fill_silence);
	else
		return noninterleaved_copy(substream, off, NULL, 0, frames,
					   fill_silence);
Linus Torvalds's avatar
Linus Torvalds committed
2042
2043
}

2044
2045
/* sanity-check for read/write methods */
static int pcm_sanity_check(struct snd_pcm_substream *substream)
Linus Torvalds's avatar
Linus Torvalds committed
2046
{
2047
	struct snd_pcm_runtime *runtime;
2048
2049
	if (PCM_RUNTIME_CHECK(substream))
		return -ENXIO;
Linus Torvalds's avatar
Linus Torvalds committed
2050
	runtime = substream->runtime;
Takashi Iwai's avatar
Takashi Iwai committed
2051
	if (snd_BUG_ON(!substream->ops->copy_user && !runtime->dma_area))
2052
		return -EINVAL;
Linus Torvalds's avatar
Linus Torvalds committed
2053
2054
	if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
		return -EBADFD;
2055
2056
2057
	return 0;
}

2058
static int pcm_accessible_state(struct snd_pcm_runtime *runtime)
2059
{
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
	switch (runtime->status->state) {
	case SNDRV_PCM_STATE_PREPARED:
	case SNDRV_PCM_STATE_RUNNING:
	case SNDRV_PCM_STATE_PAUSED:
		return 0;
	case SNDRV_PCM_STATE_XRUN:
		return -EPIPE;
	case SNDRV_PCM_STATE_SUSPENDED:
		return -ESTRPIPE;
	default:
		return -EBADFD;
	}
Linus Torvalds's avatar
Linus Torvalds committed
2072
2073
}

2074
2075
2076
2077
2078
/* update to the given appl_ptr and call ack callback if needed;
 * when an error is returned, take back to the original value
 */
int pcm_lib_apply_appl_ptr(struct snd_pcm_substream *substream,
			   snd_pcm_uframes_t appl_ptr)
Linus Torvalds's avatar
Linus Torvalds committed
2079
{
2080
	struct snd_pcm_runtime *runtime = substream->runtime;
2081
2082
2083
	snd_pcm_uframes_t old_appl_ptr = runtime->control->appl_ptr;
	int ret;

2084
2085
2086
	if (old_appl_ptr == appl_ptr)
		return 0;

2087
2088
2089
2090
2091
2092
	runtime->control->appl_ptr = appl_ptr;
	if (substream->ops->ack) {
		ret = substream->ops->ack(substream);
		if (ret < 0) {
			runtime->control->appl_ptr = old_appl_ptr;
			return ret;
Linus Torvalds's avatar
Linus Torvalds committed
2093
2094
		}
	}
2095
2096
2097

	trace_applptr(substream, old_appl_ptr, appl_ptr);

Linus Torvalds's avatar
Linus Torvalds committed
2098
2099
	return 0;
}
2100

2101
2102
2103
/* the common loop for read/write data */
snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream,
				     void *data, bool interleaved,
2104
				     snd_pcm_uframes_t size, bool in_kernel)
Linus Torvalds's avatar
Linus Torvalds committed
2105
{
2106
	struct snd_pcm_runtime *runtime = substream->runtime;
Linus Torvalds's avatar
Linus Torvalds committed
2107
2108
	snd_pcm_uframes_t xfer = 0;
	snd_pcm_uframes_t offset = 0;
2109
	snd_pcm_uframes_t avail;
2110
2111
	pcm_copy_f writer;
	pcm_transfer_f transfer;
2112
	bool nonblock;
2113
	bool is_playback;
2114
	int err;
Linus Torvalds's avatar
Linus Torvalds committed
2115

2116
2117
2118
	err = pcm_sanity_check(substream);
	if (err < 0)
		return err;
2119

2120
	is_playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
2121
2122
2123
2124
	if (interleaved) {
		if (runtime->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED &&
		    runtime->channels > 1)
			return -EINVAL;
2125
		writer = interleaved_copy;
Linus Torvalds's avatar
Linus Torvalds committed
2126
	} else {
2127
2128
		if (runtime->access != SNDRV_PCM_ACCESS_RW_NONINTERLEAVED)
			return -EINVAL;
2129
		writer = noninterleaved_copy;
Linus Torvalds's avatar
Linus Torvalds committed
2130
2131
	}

2132
	if (!data) {
2133
2134
2135
2136
		if (is_playback)
			transfer = fill_silence;
		else
			return -EINVAL;
2137
2138
2139
2140
2141
2142
	} else if (in_kernel) {
		if (substream->ops->copy_kernel)
			transfer = substream->ops->copy_kernel;
		else
			transfer = is_playback ?
				default_write_copy_kernel : default_read_copy_kernel;
2143
2144
2145
2146
	} else {
		if (substream->ops->copy_user)
			transfer = (pcm_transfer_f)substream->ops->copy_user;
		else
2147
2148
			transfer = is_playback ?
				default_write_copy : default_read_copy;
2149
	}
Linus Torvalds's avatar
Linus Torvalds committed
2150
2151
2152
2153

	if (size == 0)
		return 0;

2154
2155
	nonblock = !!(substream->f_flags & O_NONBLOCK);

Linus Torvalds's avatar
Linus Torvalds committed
2156
	snd_pcm_stream_lock_irq(substream);
2157
2158
	err = pcm_accessible_state(runtime);
	if (err < 0)
Linus Torvalds's avatar
Linus Torvalds committed
2159
2160
		goto _end_unlock;

2161
2162
	if (!is_playback &&
	    runtime->status->state == SNDRV_PCM_STATE_PREPARED &&
2163
2164
2165
2166
	    size >= runtime->start_threshold) {
		err = snd_pcm_start(substream);
		if (err < 0)
			goto _end_unlock;
Linus Torvalds's avatar
Linus Torvalds committed
2167
2168
	}

2169
	runtime->twake = runtime->control->avail_min ? : 1;
2170
2171
	if (runtime->status->state == SNDRV_PCM_STATE_RUNNING)
		snd_pcm_update_hw_ptr(substream);
2172
	avail = snd_pcm_avail(substream);
Linus Torvalds's avatar
Linus Torvalds committed
2173
2174
2175
	while (size > 0) {
		snd_pcm_uframes_t frames, appl_ptr, appl_ofs;
		snd_pcm_uframes_t cont;
2176
		if (!avail) {
2177
2178
			if (!is_playback &&
			    runtime->status->state == SNDRV_PCM_STATE_DRAINING) {
2179
				snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
Linus Torvalds's avatar
Linus Torvalds committed
2180
2181
2182
2183
2184
2185
				goto _end_unlock;
			}
			if (nonblock) {
				err = -EAGAIN;
				goto _end_unlock;
			}
2186
2187
2188
			runtime->twake = min_t(snd_pcm_uframes_t, size,
					runtime->control->avail_min ? : 1);
			err = wait_for_avail(substream, &avail);
2189
			if (err < 0)
2190
				goto _end_unlock;
2191
2192
			if (!avail)
				continue; /* draining */
Linus Torvalds's avatar
Linus Torvalds committed
2193
2194
		}
		frames = size > avail ? avail : size;
2195
2196
2197
		appl_ptr = READ_ONCE(runtime->control->appl_ptr);
		appl_ofs = appl_ptr % runtime->buffer_size;
		cont = runtime->buffer_size - appl_ofs;
Linus Torvalds's avatar
Linus Torvalds committed
2198
2199
		if (frames > cont)
			frames = cont;
2200
		if (snd_BUG_ON(!frames)) {
2201
			runtime->twake = 0;
2202
2203
2204
			snd_pcm_stream_unlock_irq(substream);
			return -EINVAL;
		}
Linus Torvalds's avatar
Linus Torvalds committed
2205
		snd_pcm_stream_unlock_irq(substream);
2206
		err = writer(substream, appl_ofs, data, offset, frames,
2207
			     transfer);
Linus Torvalds's avatar
Linus Torvalds committed
2208
		snd_pcm_stream_lock_irq(substream);
2209
2210
		if (err < 0)
			goto _end_unlock;
2211
2212
		err = pcm_accessible_state(runtime);
		if (err < 0)
Linus Torvalds's avatar
Linus Torvalds committed
2213
2214
2215
2216
			goto _end_unlock;
		appl_ptr += frames;
		if (appl_ptr >= runtime->boundary)
			appl_ptr -= runtime->boundary;
2217
2218
2219
		err = pcm_lib_apply_appl_ptr(substream, appl_ptr);
		if (err < 0)
			goto _end_unlock;
Linus Torvalds's avatar
Linus Torvalds committed
2220
2221
2222
2223

		offset += frames;
		size -= frames;
		xfer += frames;
2224
		avail -= frames;
2225
2226
2227
2228
2229
2230
2231
		if (is_playback &&
		    runtime->status->state == SNDRV_PCM_STATE_PREPARED &&
		    snd_pcm_playback_hw_avail(runtime) >= (snd_pcm_sframes_t)runtime->start_threshold) {
			err = snd_pcm_start(substream);
			if (err < 0)
				goto _end_unlock;
		}
Linus Torvalds's avatar
Linus Torvalds committed
2232
2233
	}
 _end_unlock:
2234
	runtime->twake = 0;
2235
2236
	if (xfer > 0 && err >= 0)
		snd_pcm_update_state(substream, runtime);
Linus Torvalds's avatar
Linus Torvalds committed
2237
2238
2239
	snd_pcm_stream_unlock_irq(substream);
	return xfer > 0 ? (snd_pcm_sframes_t)xfer : err;
}
2240
EXPORT_SYMBOL(__snd_pcm_lib_xfer);
2241
2242
2243
2244
2245
2246
2247
2248

/*
 * standard channel mapping helpers
 */

/* default channel maps for multi-channel playbacks, up to 8 channels */
const struct snd_pcm_chmap_elem snd_pcm_std_chmaps[] = {
	{ .channels = 1,
2249
	  .map = { SNDRV_CHMAP_MONO } },
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
	{ .channels = 2,
	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } },
	{ .channels = 4,
	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
		   SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
	{ .channels = 6,
	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
		   SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
		   SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE } },
	{ .channels = 8,
	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
		   SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
		   SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE,
		   SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } },
	{ }
};
EXPORT_SYMBOL_GPL(snd_pcm_std_chmaps);

/* alternative channel maps with CLFE <-> surround swapped for 6/8 channels */
const struct snd_pcm_chmap_elem snd_pcm_alt_chmaps[] = {
	{ .channels = 1,
2271
	  .map = { SNDRV_CHMAP_MONO } },
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
	{ .channels = 2,
	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } },
	{ .channels = 4,
	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
		   SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
	{ .channels = 6,
	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
		   SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE,
		   SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
	{ .channels = 8,
	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
		   SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE,
		   SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
		   SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } },
	{ }
};
EXPORT_SYMBOL_GPL(snd_pcm_alt_chmaps);

static bool valid_chmap_channels(const struct snd_pcm_chmap *info, int ch)
{
	if (ch > info->max_channels)
		return false;
	return !info->channel_mask || (info->channel_mask & (1U << ch));
}

static int pcm_chmap_ctl_info(struct snd_kcontrol *kcontrol,
			      struct snd_ctl_elem_info *uinfo)
{
	struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);

	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 0;
	uinfo->count = info->max_channels;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = SNDRV_CHMAP_LAST;
	return 0;
}

/* get callback for channel map ctl element
 * stores the channel position firstly matching with the current channels
 */
static int pcm_chmap_ctl_get(struct snd_kcontrol *kcontrol,
			     struct snd_ctl_elem_value *ucontrol)
{
	struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
	unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
	struct snd_pcm_substream *substream;
	const struct snd_pcm_chmap_elem *map;

2321
	if (!info->chmap)
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
		return -EINVAL;
	substream = snd_pcm_chmap_substream(info, idx);
	if (!substream)
		return -ENODEV;
	memset(ucontrol->value.integer.value, 0,
	       sizeof(ucontrol->value.integer.value));
	if (!substream->runtime)
		return 0; /* no channels set */
	for (map = info->chmap; map->channels; map++) {
		int i;
		if (map->channels == substream->runtime->channels &&
		    valid_chmap_channels(info, map->channels)) {
			for (i = 0; i < map->channels; i++)
				ucontrol->value.integer.value[i] = map->map[i];
			return 0;
		}
	}
	return -EINVAL;
}

/* tlv callback for channel map ctl element
 * expands the pre-defined channel maps in a form of TLV
 */
static int pcm_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
			     unsigned int size, unsigned int __user *tlv)
{
	struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
	const struct snd_pcm_chmap_elem *map;
	unsigned int __user *dst;
	int c, count = 0;

2353
	if (!info->chmap)
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
		return -EINVAL;
	if (size < 8)
		return -ENOMEM;
	if (put_user(SNDRV_CTL_TLVT_CONTAINER, tlv))
		return -EFAULT;
	size -= 8;
	dst = tlv + 2;
	for (map = info->chmap; map->channels; map++) {
		int chs_bytes = map->channels * 4;
		if (!valid_chmap_channels(info, map->channels))
			continue;
		if (size < 8)
			return -ENOMEM;
		if (put_user(SNDRV_CTL_TLVT_CHMAP_FIXED, dst) ||
		    put_user(chs_bytes, dst + 1))
			return -EFAULT;
		dst += 2;
		size -= 8;
		count += 8;
		if (size < chs_bytes)
			return -ENOMEM;
		size -= chs_bytes;
		count += chs_bytes;
		for (c = 0; c < map->channels; c++) {
			if (put_user(map->map[c], dst))
				return -EFAULT;
			dst++;
		}
	}
	if (put_user(count, tlv + 1))
		return -EFAULT;
	return 0;
}

static void pcm_chmap_ctl_private_free(struct snd_kcontrol *kcontrol)
{
	struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
	info->pcm->streams[info->stream].chmap_kctl = NULL;
	kfree(info);
}

/**
 * snd_pcm_add_chmap_ctls - create channel-mapping control elements
 * @pcm: the assigned PCM instance
 * @stream: stream direction
 * @chmap: channel map elements (for query)
 * @max_channels: the max number of channels for the stream
 * @private_value: the value passed to each kcontrol's private_value field
 * @info_ret: store struct snd_pcm_chmap instance if non-NULL
 *
 * Create channel-mapping control elements assigned to the given PCM stream(s).
2405
 * Return: Zero if successful, or a negative error value.
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
 */
int snd_pcm_add_chmap_ctls(struct snd_pcm *pcm, int stream,
			   const struct snd_pcm_chmap_elem *chmap,
			   int max_channels,
			   unsigned long private_value,
			   struct snd_pcm_chmap **info_ret)
{
	struct snd_pcm_chmap *info;
	struct snd_kcontrol_new knew = {
		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
		.access = SNDRV_CTL_ELEM_ACCESS_READ |
			SNDRV_CTL_ELEM_ACCESS_TLV_READ |
			SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK,
		.info = pcm_chmap_ctl_info,
		.get = pcm_chmap_ctl_get,
		.tlv.c = pcm_chmap_ctl_tlv,
	};
	int err;

2425
2426
	if (WARN_ON(pcm->streams[stream].chmap_kctl))
		return -EBUSY;
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
	info = kzalloc(sizeof(*info), GFP_KERNEL);
	if (!info)
		return -ENOMEM;
	info->pcm = pcm;
	info->stream = stream;
	info->chmap = chmap;
	info->max_channels = max_channels;
	if (stream == SNDRV_PCM_STREAM_PLAYBACK)
		knew.name = "Playback Channel Map";
	else
		knew.name = "Capture Channel Map";
	knew.device = pcm->device;
	knew.count = pcm->streams[stream].substream_count;
	knew.private_value = private_value;
	info->kctl = snd_ctl_new1(&knew, info);
	if (!info->kctl) {
		kfree(info);
		return -ENOMEM;
	}
	info->kctl->private_free = pcm_chmap_ctl_private_free;
	err = snd_ctl_add(pcm->card, info->kctl);
	if (err < 0)
		return err;
	pcm->streams[stream].chmap_kctl = info->kctl;
	if (info_ret)
		*info_ret = info;
	return 0;
}
EXPORT_SYMBOL_GPL(snd_pcm_add_chmap_ctls);
For faster browsing, not all history is shown. View entire blame