Initial Contribution

msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142

Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/arch/arm/mach-msm/qdsp6v2/audio_utils.c b/arch/arm/mach-msm/qdsp6v2/audio_utils.c
new file mode 100644
index 0000000..2e31ba0
--- /dev/null
+++ b/arch/arm/mach-msm/qdsp6v2/audio_utils.c
@@ -0,0 +1,639 @@
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+*/
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <asm/atomic.h>
+#include <asm/ioctls.h>
+#include <sound/q6asm.h>
+#include <sound/apr_audio.h>
+#include "audio_utils.h"
+
+static int audio_in_pause(struct q6audio_in  *audio)
+{
+	int rc;
+
+	rc = q6asm_cmd(audio->ac, CMD_PAUSE);
+	if (rc < 0)
+		pr_err("%s:session id %d: pause cmd failed rc=%d\n", __func__,
+				audio->ac->session, rc);
+
+	return rc;
+}
+
+static int audio_in_flush(struct q6audio_in  *audio)
+{
+	int rc;
+
+	pr_debug("%s:session id %d: flush\n", __func__, audio->ac->session);
+	/* Implicitly issue a pause to the decoder before flushing */
+	rc = audio_in_pause(audio);
+	if (rc < 0) {
+		pr_err("%s:session id %d: pause cmd failed rc=%d\n", __func__,
+				audio->ac->session, rc);
+		return rc;
+	}
+
+	rc = q6asm_cmd(audio->ac, CMD_FLUSH);
+	if (rc < 0) {
+		pr_err("%s:session id %d: flush cmd failed rc=%d\n", __func__,
+				audio->ac->session, rc);
+		return rc;
+	}
+	audio->rflush = 1;
+	audio->wflush = 1;
+	memset(audio->out_frame_info, 0, sizeof(audio->out_frame_info));
+	wake_up(&audio->read_wait);
+	/* get read_lock to ensure no more waiting read thread */
+	mutex_lock(&audio->read_lock);
+	audio->rflush = 0;
+	mutex_unlock(&audio->read_lock);
+	wake_up(&audio->write_wait);
+	/* get write_lock to ensure no more waiting write thread */
+	mutex_lock(&audio->write_lock);
+	audio->wflush = 0;
+	mutex_unlock(&audio->write_lock);
+	pr_debug("%s:session id %d: in_bytes %d\n", __func__,
+			audio->ac->session, atomic_read(&audio->in_bytes));
+	pr_debug("%s:session id %d: in_samples %d\n", __func__,
+			audio->ac->session, atomic_read(&audio->in_samples));
+	atomic_set(&audio->in_bytes, 0);
+	atomic_set(&audio->in_samples, 0);
+	return 0;
+}
+
+void  audio_in_get_dsp_frames(struct q6audio_in *audio,
+	uint32_t token,	uint32_t *payload)
+{
+	uint32_t index;
+
+	index = token;
+	pr_debug("%s:session id %d: index=%d nr frames=%d offset[%d]\n",
+			__func__, audio->ac->session, token, payload[7],
+			payload[3]);
+	pr_debug("%s:session id %d: timemsw=%d lsw=%d\n", __func__,
+			audio->ac->session, payload[4], payload[5]);
+	pr_debug("%s:session id %d: uflags=0x%8x uid=0x%8x\n", __func__,
+			audio->ac->session, payload[6], payload[8]);
+	pr_debug("%s:session id %d: enc frame size=0x%8x\n", __func__,
+			audio->ac->session, payload[2]);
+
+	audio->out_frame_info[index][0] = payload[7];
+	audio->out_frame_info[index][1] = payload[3];
+
+	/* statistics of read */
+	atomic_add(payload[2], &audio->in_bytes);
+	atomic_add(payload[7], &audio->in_samples);
+
+	if (atomic_read(&audio->out_count) <= audio->str_cfg.buffer_count) {
+		atomic_inc(&audio->out_count);
+		wake_up(&audio->read_wait);
+	}
+}
+
+/* must be called with audio->lock held */
+int audio_in_enable(struct q6audio_in  *audio)
+{
+	if (audio->enabled)
+		return 0;
+
+	/* 2nd arg: 0 -> run immediately
+		3rd arg: 0 -> msw_ts, 4th arg: 0 ->lsw_ts */
+	return q6asm_run(audio->ac, 0x00, 0x00, 0x00);
+}
+
+/* must be called with audio->lock held */
+int audio_in_disable(struct q6audio_in  *audio)
+{
+	int rc = 0;
+	if (audio->opened) {
+		audio->enabled = 0;
+		audio->opened = 0;
+		pr_debug("%s:session id %d: inbytes[%d] insamples[%d]\n",
+				__func__, audio->ac->session,
+				atomic_read(&audio->in_bytes),
+				atomic_read(&audio->in_samples));
+
+		rc = q6asm_cmd(audio->ac, CMD_CLOSE);
+		if (rc < 0)
+			pr_err("%s:session id %d: Failed to close the\
+				session rc=%d\n", __func__, audio->ac->session,
+				rc);
+		audio->stopped = 1;
+		memset(audio->out_frame_info, 0,
+				sizeof(audio->out_frame_info));
+		wake_up(&audio->read_wait);
+		wake_up(&audio->write_wait);
+	}
+	pr_debug("%s:session id %d: enabled[%d]\n", __func__,
+			audio->ac->session, audio->enabled);
+	return rc;
+}
+
+int audio_in_buf_alloc(struct q6audio_in *audio)
+{
+	int rc = 0;
+
+	switch (audio->buf_alloc) {
+	case NO_BUF_ALLOC:
+		if (audio->feedback == NON_TUNNEL_MODE) {
+			rc = q6asm_audio_client_buf_alloc(IN,
+				audio->ac,
+				ALIGN_BUF_SIZE(audio->pcm_cfg.buffer_size),
+				audio->pcm_cfg.buffer_count);
+			if (rc < 0) {
+				pr_err("%s:session id %d: Buffer Alloc\
+						failed\n", __func__,
+						audio->ac->session);
+				rc = -ENOMEM;
+				break;
+			}
+			audio->buf_alloc |= BUF_ALLOC_IN;
+		}
+		rc = q6asm_audio_client_buf_alloc(OUT, audio->ac,
+				ALIGN_BUF_SIZE(audio->str_cfg.buffer_size),
+				audio->str_cfg.buffer_count);
+		if (rc < 0) {
+			pr_err("%s:session id %d: Buffer Alloc failed rc=%d\n",
+					__func__, audio->ac->session, rc);
+			rc = -ENOMEM;
+			break;
+		}
+		audio->buf_alloc |= BUF_ALLOC_OUT;
+		break;
+	case BUF_ALLOC_IN:
+		rc = q6asm_audio_client_buf_alloc(OUT, audio->ac,
+				ALIGN_BUF_SIZE(audio->str_cfg.buffer_size),
+				audio->str_cfg.buffer_count);
+		if (rc < 0) {
+			pr_err("%s:session id %d: Buffer Alloc failed rc=%d\n",
+					__func__, audio->ac->session, rc);
+			rc = -ENOMEM;
+			break;
+		}
+		audio->buf_alloc |= BUF_ALLOC_OUT;
+		break;
+	case BUF_ALLOC_OUT:
+		if (audio->feedback == NON_TUNNEL_MODE) {
+			rc = q6asm_audio_client_buf_alloc(IN, audio->ac,
+				ALIGN_BUF_SIZE(audio->pcm_cfg.buffer_size),
+				audio->pcm_cfg.buffer_count);
+			if (rc < 0) {
+				pr_err("%s:session id %d: Buffer Alloc\
+					failed\n", __func__,
+					audio->ac->session);
+				rc = -ENOMEM;
+				break;
+			}
+			audio->buf_alloc |= BUF_ALLOC_IN;
+		}
+		break;
+	default:
+		pr_debug("%s:session id %d: buf[%d]\n", __func__,
+					audio->ac->session, audio->buf_alloc);
+	}
+
+	return rc;
+}
+/* ------------------- device --------------------- */
+long audio_in_ioctl(struct file *file,
+				unsigned int cmd, unsigned long arg)
+{
+	struct q6audio_in  *audio = file->private_data;
+	int rc = 0;
+
+	if (cmd == AUDIO_GET_STATS) {
+		struct msm_audio_stats stats;
+		stats.byte_count = atomic_read(&audio->in_bytes);
+		stats.sample_count = atomic_read(&audio->in_samples);
+		if (copy_to_user((void *) arg, &stats, sizeof(stats)))
+			return -EFAULT;
+		return rc;
+	}
+
+	mutex_lock(&audio->lock);
+	switch (cmd) {
+	case AUDIO_FLUSH: {
+		/* Make sure we're stopped and we wake any threads
+		* that might be blocked holding the read_lock.
+		* While audio->stopped read threads will always
+		* exit immediately.
+		*/
+		rc = audio_in_flush(audio);
+		if (rc < 0)
+			pr_err("%s:session id %d: Flush Fail rc=%d\n",
+					__func__, audio->ac->session, rc);
+		break;
+	}
+	case AUDIO_PAUSE: {
+		pr_debug("%s:session id %d: AUDIO_PAUSE\n", __func__,
+					audio->ac->session);
+		if (audio->enabled)
+			audio_in_pause(audio);
+		break;
+	}
+	case AUDIO_GET_STREAM_CONFIG: {
+		struct msm_audio_stream_config cfg;
+		memset(&cfg, 0, sizeof(cfg));
+		cfg.buffer_size = audio->str_cfg.buffer_size;
+		cfg.buffer_count = audio->str_cfg.buffer_count;
+		if (copy_to_user((void *)arg, &cfg, sizeof(cfg)))
+			rc = -EFAULT;
+		pr_debug("%s:session id %d: AUDIO_GET_STREAM_CONFIG %d %d\n",
+				__func__, audio->ac->session, cfg.buffer_size,
+				cfg.buffer_count);
+		break;
+	}
+	case AUDIO_SET_STREAM_CONFIG: {
+		struct msm_audio_stream_config cfg;
+		if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) {
+			rc = -EFAULT;
+			break;
+		}
+		/* Minimum single frame size,
+		   but with in maximum frames number */
+		if ((cfg.buffer_size < (audio->min_frame_size+ \
+			sizeof(struct meta_out_dsp))) ||
+			(cfg.buffer_count < FRAME_NUM)) {
+			rc = -EINVAL;
+			break;
+		}
+		audio->str_cfg.buffer_size = cfg.buffer_size;
+		audio->str_cfg.buffer_count = cfg.buffer_count;
+		rc = q6asm_audio_client_buf_alloc(OUT, audio->ac,
+				ALIGN_BUF_SIZE(audio->str_cfg.buffer_size),
+				audio->str_cfg.buffer_count);
+		if (rc < 0) {
+			pr_err("%s: session id %d: Buffer Alloc failed rc=%d\n",
+					__func__, audio->ac->session, rc);
+			rc = -ENOMEM;
+			break;
+		}
+		audio->buf_alloc |= BUF_ALLOC_OUT;
+		rc = 0;
+		pr_debug("%s:session id %d: AUDIO_SET_STREAM_CONFIG %d %d\n",
+				__func__, audio->ac->session,
+				audio->str_cfg.buffer_size,
+				audio->str_cfg.buffer_count);
+		break;
+	}
+	case AUDIO_GET_SESSION_ID: {
+		if (copy_to_user((void *) arg, &audio->ac->session,
+			sizeof(unsigned short))) {
+			rc = -EFAULT;
+		}
+		break;
+	}
+	case AUDIO_SET_BUF_CFG: {
+		struct msm_audio_buf_cfg  cfg;
+		if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) {
+			rc = -EFAULT;
+			break;
+		}
+		if ((audio->feedback == NON_TUNNEL_MODE) &&
+			!cfg.meta_info_enable) {
+			rc = -EFAULT;
+			break;
+		}
+
+		/* Restrict the num of frames per buf to coincide with
+		 * default buf size */
+		if (cfg.frames_per_buf > audio->max_frames_per_buf) {
+			rc = -EFAULT;
+			break;
+		}
+		audio->buf_cfg.meta_info_enable = cfg.meta_info_enable;
+		audio->buf_cfg.frames_per_buf = cfg.frames_per_buf;
+		pr_debug("%s:session id %d: Set-buf-cfg: meta[%d]\
+				framesperbuf[%d]\n", __func__,
+				audio->ac->session, cfg.meta_info_enable,
+				cfg.frames_per_buf);
+		break;
+	}
+	case AUDIO_GET_BUF_CFG: {
+		pr_debug("%s:session id %d: Get-buf-cfg: meta[%d]\
+			framesperbuf[%d]\n", __func__,
+			audio->ac->session, audio->buf_cfg.meta_info_enable,
+			audio->buf_cfg.frames_per_buf);
+
+		if (copy_to_user((void *)arg, &audio->buf_cfg,
+					sizeof(struct msm_audio_buf_cfg)))
+			rc = -EFAULT;
+		break;
+	}
+	case AUDIO_GET_CONFIG: {
+		if (copy_to_user((void *)arg, &audio->pcm_cfg,
+					sizeof(struct msm_audio_config)))
+			rc = -EFAULT;
+		break;
+
+	}
+	case AUDIO_SET_CONFIG: {
+		struct msm_audio_config cfg;
+		if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) {
+			rc = -EFAULT;
+			break;
+		}
+		if (audio->feedback != NON_TUNNEL_MODE) {
+			pr_err("%s:session id %d: Not sufficient permission to"
+					"change the record mode\n", __func__,
+					audio->ac->session);
+			rc = -EACCES;
+			break;
+		}
+		if ((cfg.buffer_count > PCM_BUF_COUNT) ||
+				(cfg.buffer_count == 1))
+			cfg.buffer_count = PCM_BUF_COUNT;
+
+		audio->pcm_cfg.buffer_count = cfg.buffer_count;
+		audio->pcm_cfg.buffer_size  = cfg.buffer_size;
+		audio->pcm_cfg.channel_count = cfg.channel_count;
+		audio->pcm_cfg.sample_rate = cfg.sample_rate;
+		rc = q6asm_audio_client_buf_alloc(IN, audio->ac,
+			ALIGN_BUF_SIZE(audio->pcm_cfg.buffer_size),
+			audio->pcm_cfg.buffer_count);
+		if (rc < 0) {
+			pr_err("%s:session id %d: Buffer Alloc failed\n",
+				__func__, audio->ac->session);
+			rc = -ENOMEM;
+			break;
+		}
+		audio->buf_alloc |= BUF_ALLOC_IN;
+		rc = 0;
+		pr_debug("%s:session id %d: AUDIO_SET_CONFIG %d %d\n", __func__,
+			audio->ac->session, audio->pcm_cfg.buffer_count,
+			audio->pcm_cfg.buffer_size);
+		break;
+	}
+	default:
+		/* call codec specific ioctl */
+		rc = audio->enc_ioctl(file, cmd, arg);
+	}
+	mutex_unlock(&audio->lock);
+	return rc;
+}
+
+ssize_t audio_in_read(struct file *file,
+				char __user *buf,
+				size_t count, loff_t *pos)
+{
+	struct q6audio_in  *audio = file->private_data;
+	const char __user *start = buf;
+	unsigned char *data;
+	uint32_t offset = 0;
+	uint32_t size = 0;
+	int rc = 0;
+	uint32_t idx;
+	struct meta_out_dsp meta;
+	uint32_t bytes_to_copy = 0;
+	uint32_t mfield_size = (audio->buf_cfg.meta_info_enable == 0) ? 0 :
+		(sizeof(unsigned char) +
+		(sizeof(struct meta_out_dsp)*(audio->buf_cfg.frames_per_buf)));
+
+	pr_debug("%s:session id %d: read - %d\n", __func__, audio->ac->session,
+			count);
+	if (!audio->enabled)
+		return -EFAULT;
+	mutex_lock(&audio->read_lock);
+	while (count > 0) {
+		rc = wait_event_interruptible(
+			audio->read_wait,
+			((atomic_read(&audio->out_count) > 0) ||
+			(audio->stopped) ||
+			 audio->rflush || audio->eos_rsp));
+
+		if (rc < 0)
+			break;
+
+		if ((audio->stopped && !(atomic_read(&audio->out_count))) ||
+			audio->rflush) {
+			pr_debug("%s:session id %d: driver in stop state or\
+				flush,No more buf to read", __func__,
+				audio->ac->session);
+			rc = 0;/* End of File */
+			break;
+		}
+		if (!(atomic_read(&audio->out_count)) &&
+			(audio->eos_rsp == 1) &&
+			(count >= (sizeof(unsigned char) +
+				sizeof(struct meta_out_dsp)))) {
+			unsigned char num_of_frames;
+			pr_info("%s:session id %d: eos %d at output\n",
+				__func__, audio->ac->session, audio->eos_rsp);
+			if (buf != start)
+				break;
+			num_of_frames = 0xFF;
+			if (copy_to_user(buf, &num_of_frames,
+					sizeof(unsigned char))) {
+				rc = -EFAULT;
+				break;
+			}
+			buf += sizeof(unsigned char);
+			meta.frame_size = 0xFFFF;
+			meta.encoded_pcm_samples = 0xFFFF;
+			meta.msw_ts = 0x00;
+			meta.lsw_ts = 0x00;
+			meta.nflags = AUD_EOS_SET;
+			audio->eos_rsp = 0;
+			if (copy_to_user(buf, &meta, sizeof(meta))) {
+				rc = -EFAULT;
+				break;
+			}
+			buf += sizeof(meta);
+			break;
+		}
+		data = (unsigned char *)q6asm_is_cpu_buf_avail(OUT, audio->ac,
+						&size, &idx);
+		if ((count >= (size + mfield_size)) && data) {
+			if (audio->buf_cfg.meta_info_enable) {
+				if (copy_to_user(buf,
+					&audio->out_frame_info[idx][0],
+					sizeof(unsigned char))) {
+					rc = -EFAULT;
+					break;
+				}
+				bytes_to_copy =
+					(size + audio->out_frame_info[idx][1]);
+				/* Number of frames information copied */
+				buf += sizeof(unsigned char);
+				count -= sizeof(unsigned char);
+			} else {
+				offset = audio->out_frame_info[idx][1];
+				bytes_to_copy = size;
+			}
+
+			pr_debug("%s:session id %d: offset=%d nr of frames= %d\n",
+					__func__, audio->ac->session,
+					audio->out_frame_info[idx][1],
+					audio->out_frame_info[idx][0]);
+
+			if (copy_to_user(buf, &data[offset], bytes_to_copy)) {
+				rc = -EFAULT;
+				break;
+			}
+			count -= bytes_to_copy;
+			buf += bytes_to_copy;
+		} else {
+			pr_err("%s:session id %d: short read data[%p]\
+				bytesavail[%d]bytesrequest[%d]\n", __func__,
+				audio->ac->session,
+				data, size, count);
+		}
+		atomic_dec(&audio->out_count);
+		q6asm_read(audio->ac);
+		break;
+	}
+	mutex_unlock(&audio->read_lock);
+
+	pr_debug("%s:session id %d: read: %d bytes\n", __func__,
+			audio->ac->session, (buf-start));
+	if (buf > start)
+		return buf - start;
+	return rc;
+}
+
+static int extract_meta_info(char *buf, unsigned long *msw_ts,
+		unsigned long *lsw_ts, unsigned int *flags)
+{
+	struct meta_in *meta = (struct meta_in *)buf;
+	*msw_ts = meta->ntimestamp.highpart;
+	*lsw_ts = meta->ntimestamp.lowpart;
+	*flags = meta->nflags;
+	return 0;
+}
+
+ssize_t audio_in_write(struct file *file,
+		const char __user *buf,
+		size_t count, loff_t *pos)
+{
+	struct q6audio_in *audio = file->private_data;
+	const char __user *start = buf;
+	size_t xfer = 0;
+	char *cpy_ptr;
+	int rc = 0;
+	unsigned char *data;
+	uint32_t size = 0;
+	uint32_t idx = 0;
+	uint32_t nflags = 0;
+	unsigned long msw_ts = 0;
+	unsigned long lsw_ts = 0;
+	uint32_t mfield_size = (audio->buf_cfg.meta_info_enable == 0) ? 0 :
+			sizeof(struct meta_in);
+
+	pr_debug("%s:session id %d: to write[%d]\n", __func__,
+			audio->ac->session, count);
+	if (!audio->enabled)
+		return -EFAULT;
+	mutex_lock(&audio->write_lock);
+
+	while (count > 0) {
+		rc = wait_event_interruptible(audio->write_wait,
+				     ((atomic_read(&audio->in_count) > 0) ||
+				      (audio->stopped) ||
+				      (audio->wflush)));
+		if (rc < 0)
+			break;
+		if (audio->stopped || audio->wflush) {
+			pr_debug("%s: session id %d: stop or flush\n", __func__,
+					audio->ac->session);
+			rc = -EBUSY;
+			break;
+		}
+		data = (unsigned char *)q6asm_is_cpu_buf_avail(IN, audio->ac,
+						&size, &idx);
+		if (!data) {
+			pr_debug("%s:session id %d: No buf available\n",
+				__func__, audio->ac->session);
+			continue;
+		}
+		cpy_ptr = data;
+		if (audio->buf_cfg.meta_info_enable) {
+			if (buf == start) {
+				/* Processing beginning of user buffer */
+				if (copy_from_user(cpy_ptr, buf, mfield_size)) {
+					rc = -EFAULT;
+					break;
+				}
+				/* Check if EOS flag is set and buffer has
+				* contains just meta field
+				*/
+				extract_meta_info(cpy_ptr, &msw_ts, &lsw_ts,
+						&nflags);
+				buf += mfield_size;
+				if (count == mfield_size) {
+					/* send the EOS and return */
+					pr_debug("%s:session id %d: send EOS\
+						0x%8x\n", __func__,
+						audio->ac->session, nflags);
+					break;
+				}
+				count -= mfield_size;
+			} else {
+				pr_debug("%s:session id %d: continuous\
+				buffer\n", __func__, audio->ac->session);
+			}
+		}
+		xfer = (count > (audio->pcm_cfg.buffer_size)) ?
+				(audio->pcm_cfg.buffer_size) : count;
+
+		if (copy_from_user(cpy_ptr, buf, xfer)) {
+			rc = -EFAULT;
+			break;
+		}
+		rc = q6asm_write(audio->ac, xfer, msw_ts, lsw_ts, 0x00);
+		if (rc < 0) {
+			rc = -EFAULT;
+			break;
+		}
+		atomic_dec(&audio->in_count);
+		count -= xfer;
+		buf += xfer;
+	}
+	mutex_unlock(&audio->write_lock);
+	pr_debug("%s:session id %d: eos_condition 0x%8x buf[0x%x]\
+			start[0x%x]\n", __func__, audio->ac->session,
+				nflags,	(int) buf, (int) start);
+	if (nflags & AUD_EOS_SET) {
+		rc = q6asm_cmd(audio->ac, CMD_EOS);
+		pr_info("%s:session id %d: eos %d at input\n", __func__,
+				audio->ac->session, audio->eos_rsp);
+	}
+	pr_debug("%s:session id %d: Written %d Avail Buf[%d]", __func__,
+			audio->ac->session, (buf - start - mfield_size),
+			atomic_read(&audio->in_count));
+	if (!rc) {
+		if (buf > start)
+			return buf - start;
+	}
+	return rc;
+}
+
+int audio_in_release(struct inode *inode, struct file *file)
+{
+	struct q6audio_in  *audio = file->private_data;
+	pr_info("%s: session id %d\n", __func__, audio->ac->session);
+	mutex_lock(&audio->lock);
+	audio_in_disable(audio);
+	q6asm_audio_client_free(audio->ac);
+	mutex_unlock(&audio->lock);
+	kfree(audio->enc_cfg);
+	kfree(audio);
+	return 0;
+}
+