NetBSD/sys/dev/ieee1394/fwiso.c

887 lines
19 KiB
C
Raw Normal View History

/* $NetBSD: fwiso.c,v 1.2 2003/01/05 08:11:33 jmc Exp $ */
/*-
* Copyright (c) 2001 and 2002
* HAYAKAWA Koichi. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "fwiso.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/conf.h>
#include <sys/ioctl.h>
#include <sys/fcntl.h>
#include <sys/uio.h>
#include <sys/device.h>
#include <sys/errno.h>
#include <sys/signalvar.h>
#include <sys/vnode.h>
#include <sys/mbuf.h>
#include <sys/poll.h>
#include <sys/select.h>
#include <dev/ieee1394/fwiso_ioctl.h>
#include <dev/ieee1394/ieee1394reg.h>
#include <dev/ieee1394/ieee1394var.h>
#include <dev/ieee1394/fwisovar.h>
#ifdef DEBUG
#define DPRINTF(x) printf x
#else
#define DPRINTF(x)
#endif
#define UNIT(dev) minor(dev)&0xff
#define IOCTL(dev) minor(dev)&0x0100
#define DVDEV(dev) minor(dev)&0x0200
#define MPEG2DEV(dev) minor(dev)&0x0300
/*
* device number rule
*
* Lower 8-bit of minor number represents fwiso pseudo device.
* Higher bits represents types of the device.
*
* 0x0000: raw fwiso device
* 0x0100: for ioctl (can't read/write)
* 0x0200: standard DV type
* 0x0300: MPEG2 TS
*/
/*
* The movement of data.
*
* Isochronous data acquired by device is copied in reservoir
* buffer by fwisohandler. fwisohandler is a callback function,
* called in the device drover of an IEEE 1394 host device. The
* copied data in reservoir is deriverd to userland by fwisoread.
* So, at least data copy will happen twice.
*/
struct fwiso_pktdata {
u_int8_t *pkt_base; /* Base address: NEVER change */
size_t pkt_len; /* Length of the data in the buffer */
size_t pkt_buflen; /* Length of the buffer: NEVER change */
};
struct fwiso_pkts {
struct fwiso_pktdata *fp_iov; /* packet data vector: NEVER change */
struct fwiso_pktdata *fp_iov_last; /* end packet data vector: NEVER change */
/*
* Set members below volatile because they will be changed by
* both intr handler and read routine.
*/
volatile struct fwiso_pktdata *fp_iov_start; /* pkt data vector */
struct fwiso_pktdata *fp_iov_end; /* pkt data vector */
volatile int fp_iovcnt; /* vector length */
};
#define FWISO_DEVNAMESIZ 12
struct fwiso_data {
char fd_devname[FWISO_DEVNAMESIZ];
struct ieee1394_softc *fd_dev;
int fd_status; /* status: share with interrupt hendler */
int fd_overflow;
struct uio *fd_uio; /* for interrupt handler */
struct fwiso_pkts fd_rsv_pkts; /* temporary buffer for packets */
u_int8_t *fd_rsv_buf;
int fd_rsv_size;
/*
* Should those data (fd_mode, fd_channel, fd_tag) be dedicatd
* for read and write, or be shared with read and write?
*/
int fd_mode;
int fd_channel; /* ISO channel (0 - 63) */
int fd_tag; /* IEEE1394_ISO_TAG0, _TAG1, _TAG2 */
int fd_threshold;
u_int32_t fd_cycletimer;
ieee1394_ir_tag_t fd_irtag; /* tag for isochronous reception */
ieee1394_it_tag_t fd_ittag; /* tag for isochronous transmission */
struct ieee1394_it_datalist *fd_itlist;
int fd_it_serial; /* serial number of data block (not packet) */
int fd_it_frame; /* frame number */
int fd_it_dv_insert_empty;
int fd_it_dv_insert_fraction;
int fd_it_dv_insert_fractional;
int fd_flags;
#define FWISO_OPEN 0x01
#define FWISO_SETHANDLER 0x02
#define FWISO_IR_SLEEPING 0x04
#define FWISO_WRITE 0x08
#define FWISO_NONBLOCK 0x10
size_t fd_uioprev; /* previous uio value: XXX really needed? */
};
struct fwiso_data fwiso_data_str[NFWISO];
static int fwiso_min_space[FWISO_MODE_MAX] = {1, 480, 488, 1};
static int fwiso_head_offset[FWISO_MODE_MAX] = {0, 8, 0, 0};
#define FWISO_MAX_INTERFACE 10
/* I hope all the elements of fwiso_interface is initialised as NULL */
static struct ieee1394_softc *fwiso_interface[FWISO_MAX_INTERFACE] = {NULL};
static struct evcnt fwiso_drop_ev = EVCNT_INITIALIZER(EVCNT_TYPE_MISC, NULL, "fwiso", "dropframe");
static struct evcnt fwiso_frame_ev = EVCNT_INITIALIZER(EVCNT_TYPE_MISC, NULL, "fwiso", "frame");
void fwisoattach(int);
static struct ieee1394_softc *fwiso_lookup_if(const char *);
int fwisowrite_dv(struct fwiso_data *, struct uio *, int);
static int fwiso_set_handler(struct fwiso_data *);
dev_type_open(fwisoopen);
dev_type_close(fwisoclose);
dev_type_read(fwisoread);
dev_type_write(fwisowrite);
dev_type_ioctl(fwisoioctl);
dev_type_poll(fwisopoll);
const struct cdevsw fwiso_cdevsw = {
fwisoopen, fwisoclose, fwisoread, fwisowrite, fwisoioctl,
nostop, notty, fwisopoll, nommap,
};
/* XXX: only for experimental */
extern int fwohci_set_isotx(struct ieee1394_softc *);
struct fwohci_it_ctx;
extern int fwohci_it_ctx_clear(ieee1394_it_tag_t);
extern u_int16_t fwohci_it_cycletimer(ieee1394_it_tag_t);
extern ieee1394_it_tag_t fwohci_it_set(struct ieee1394_softc *, int, int);
extern int fwohci_it_ctx_writedata(ieee1394_it_tag_t, int,
struct ieee1394_it_datalist *, int);
/* ARGSUSED */
void
fwisoattach(int n)
{
int i;
memset(fwiso_data_str, 0, sizeof(fwiso_data_str));
for (i = 0; i < NFWISO; ++i) {
snprintf(fwiso_data_str[i].fd_devname, 10, "fwiso%d", i);
fwiso_data_str[i].fd_channel = -1;
fwiso_data_str[i].fd_mode = -1;
fwiso_data_str[i].fd_threshold = 0;
fwiso_data_str[i].fd_tag = 0;
fwiso_data_str[i].fd_uioprev = 0;
}
evcnt_attach_static(&fwiso_drop_ev);
evcnt_attach_static(&fwiso_frame_ev);
}
/*
* int fwisoread(dev_t dev, struct uio *uio, int ioflag)
*
* Read routine
*
* Algorithm:
*
* if interrupt handler is not set
* register interrupt handler
* prepare reserve buffer;
* if some data exists in reserve buffer
* copy data in uio
* if uio is full
* return
* set uio in interrupt handler
* sleep until interrupt handler wait me
* set reserve buffer
* return
*/
int
fwisoread(dev_t dev, struct uio *uio, int ioflag)
{
struct fwiso_data *fd;
int unit;
int status = 0;
int s;
int minspace;
int headoffs;
volatile struct fwiso_pktdata *pkt;
struct fwiso_pktdata *pkt_last;
struct ieee1394_softc *isc;
size_t resid;
int i;
unit = UNIT(dev);
if (unit >= NFWISO) {
return ENXIO;
}
if (IOCTL(dev)) {
DPRINTF(("fwiso%d cannot read\n", unit));
return ENXIO;
}
fd = &fwiso_data_str[unit];
if ((fd->fd_flags & FWISO_OPEN) == 0) {
return EBUSY;
}
isc = fd->fd_dev;
if ((fd->fd_flags & FWISO_SETHANDLER) == 0) {
int rv;
if ((rv = fwiso_set_handler(fd)) != 0) {
return rv;
}
}
minspace = fwiso_min_space[fd->fd_mode];
headoffs = fwiso_head_offset[fd->fd_mode];
/* This is constant */
pkt_last = fd->fd_rsv_pkts.fp_iov_last;
/* Only fwiso writes. Others read only */
pkt = fd->fd_rsv_pkts.fp_iov_start;
s = splbio();
i = 0;
resid = uio->uio_resid;
DPRINTF(("%s: now reading\n", isc->sc1394_dev.dv_xname));
while ((status = (*isc->sc1394_ir_read)((struct device *)isc,
fd->fd_irtag, uio, headoffs, 0)) == EAGAIN
|| (status == 0 && uio->uio_resid == resid)) {
if (fd->fd_flags & FWISO_NONBLOCK) {
break;
}
if (isc->sc1394_ir_wait == NULL) {
if (++i > 20) {
status = EIO;
break;
}
delay(100);
} else {
status = (*isc->sc1394_ir_wait)((struct device *)isc,
fd->fd_irtag, (void *)fd, fd->fd_devname);
if (status != 0) {
break;
}
}
}
splx(s);
return status;
}
/*
* int fwisowrite(dev_t dev, struct uio *uio, int ioflag)
*
* Write routine
*/
int
fwisowrite(dev_t dev, struct uio *uio, int ioflag)
{
struct fwiso_data *fd;
int unit;
int rv = ENODEV;
unit = UNIT(dev);
if (unit >= NFWISO) {
return ENXIO;
}
if (IOCTL(dev)) {
DPRINTF(("fwiso%d cannot read\n", unit));
return ENXIO;
}
fd = &fwiso_data_str[unit];
if ((fd->fd_flags & FWISO_OPEN) == 0) {
return EBUSY;
}
if (fd->fd_ittag == NULL) {
/* XXX */
if (fd->fd_channel < 0) {
fd->fd_channel = 63;
}
if (fd->fd_tag <= 0) {
fd->fd_tag = IEEE1394_ISO_TAG1;
}
fd->fd_ittag = fwohci_it_set(fd->fd_dev, fd->fd_channel,
fd->fd_tag);
if (fd->fd_ittag == NULL) {
return EBUSY;
}
fd->fd_flags |= FWISO_WRITE;
printf("fwiso%d: it_set returns %p\n", unit, fd->fd_ittag);
fd->fd_it_dv_insert_fractional = 16000;
fd->fd_it_dv_insert_fraction = 1015;
fd->fd_it_dv_insert_empty = 0;
#define ITLISTSIZE sizeof(struct ieee1394_it_datalist)*100
if ((fd->fd_itlist = malloc(ITLISTSIZE, M_DEVBUF,
M_WAITOK|M_ZERO)) == NULL) {
/* */
printf("fwiso%d: cannot get memory for it_list\n",
unit);
return EBUSY;
}
/* XXX */
fd->fd_cycletimer = fwohci_it_cycletimer(fd->fd_ittag);
printf("cycletimer %x %d %d\n", fd->fd_cycletimer,
fd->fd_cycletimer >> 13, fd->fd_cycletimer&0x1fff);
}
#if 1
switch(fd->fd_mode) {
case FWISO_MODE_DV:
rv = fwisowrite_dv(fd, uio, 480);
break;
case FWISO_MODE_DV_RAW:
break;
case FWISO_MODE_RAW:
break;
}
#endif
return rv;
}
int
fwisowrite_dv(struct fwiso_data *fd, struct uio *uio, int size)
{
int rv;
struct ieee1394_it_datalist *itlist;
struct ieee1394_it_datalist *loopend;
struct iovec *iov = uio->uio_iov;
int nodeid;
const struct iovec *iov_end = iov + uio->uio_iovcnt;
int iov_offs; /* offset in iov */
int ndata;
int writesize = 0;
int res = 0;
int serialno;
int i;
int s;
DPRINTF(("fwisowrite_dv(%p %p %d), resid %d iovcnt %d\n",
fd, uio, size, uio->uio_resid, uio->uio_iovcnt));
iov_offs = 0;
itlist = fd->fd_itlist;
if (uio->uio_resid < size) {
/* Only get rid of small data */
uio->uio_offset += uio->uio_resid;
uio->uio_resid = 0;
return res;
}
nodeid = 0;
s = splbio();
while (uio->uio_resid >= size) {
if (uio->uio_resid > 90*size) {
loopend = fd->fd_itlist + 90;
DPRINTF(("loop 90\n"));
} else {
loopend = fd->fd_itlist + uio->uio_resid/size;
DPRINTF(("loop %d %p\n", uio->uio_resid/size, loopend));
}
serialno = fd->fd_it_serial;
/* make data list */
for (itlist = fd->fd_itlist; itlist < loopend; ++itlist) {
int psize = size;
/* first data is CIP header */
itlist->it_cmd[0] = IEEE1394_IT_CMD_IMMED | 8;
itlist->it_u[0].id_data[0]
= IEEE1394_CIP_SET(SID, nodeid)
| IEEE1394_CIP_SET(DBS, size)
| IEEE1394_CIP_SET(DBC, serialno);
++serialno;
itlist->it_u[0].id_data[1]
= IEEE1394_CIP_FMT_DV
| IEEE1394_CIP_SET(FDF_SYT, 0xffff);
for (i = 1; i < IEEE1394_IT_CMD_NUM; ++i) {
if (psize == 0) {
itlist->it_cmd[i]
= IEEE1394_IT_CMD_NOP;
continue;
}
itlist->it_u[i].id_addr
= (u_int8_t *)iov->iov_base + iov_offs;
if (iov->iov_len - iov_offs >= psize) {
itlist->it_cmd[i]
= IEEE1394_IT_CMD_PTR | psize;
iov_offs += psize;
psize = 0;
} else {
itlist->it_cmd[i]
= IEEE1394_IT_CMD_PTR | (iov->iov_len - iov_offs);
psize -= iov->iov_len - iov_offs;
iov_offs = 0;
if (++iov == iov_end) {
printf("ERROR iov %d\n",
iov - uio->uio_iov);
res = EFAULT;
goto error_1;
}
}
}
}
ndata = itlist - fd->fd_itlist;
DPRINTF(("calling fwohci_it_ctx_writedata(%p %d %p 0)\n",
fd->fd_ittag, ndata, fd->fd_itlist));
rv = fwohci_it_ctx_writedata(fd->fd_ittag, ndata,
fd->fd_itlist, 0);
writesize = rv * size;
/* XXX: decrement for empty packet */
uio->uio_resid -= writesize;
uio->uio_offset += writesize;
fd->fd_it_serial += rv;
itlist += rv;
if (rv == 0) {
if (fd->fd_uioprev == uio->uio_resid) {
#if 0
printf("umm, I cannot write any more (%d)\n",
fd->fd_it_serial);
res = EFAULT;
goto error_1;
#endif
}
}
fd->fd_uioprev = uio->uio_resid;
if (rv < ndata) {
/* reach at the end of DMA buffer */
break;
}
}
error_1:
splx(s);
return res;
}
int
fwisoopen(dev_t dev, int flags, int fmt, struct proc *p)
{
struct fwiso_data *fd;
int unit = UNIT(dev);
if (unit >= NFWISO) {
return ENXIO;
}
if (IOCTL(dev)) {
return 0;
}
fd = &fwiso_data_str[unit];
if (fd->fd_flags &= FWISO_OPEN) {
return EBUSY;
}
fd->fd_flags = FWISO_OPEN;
if (flags & O_NONBLOCK) {
fd->fd_flags |= FWISO_NONBLOCK;
}
fd->fd_rsv_size = 0;
fd->fd_uio = NULL;
fd->fd_mode = FWISO_MODE_RAW;
if (DVDEV(dev)) {
fd->fd_mode = FWISO_MODE_DV;
fd->fd_channel = 63;
fd->fd_tag = IEEE1394_ISO_TAG1;
}
if (fd->fd_dev == NULL) {
int i;
for (i = 0; i < FWISO_MAX_INTERFACE; ++i) {
if (fwiso_interface[i] != NULL) {
fd->fd_dev = fwiso_interface[i];
printf("%s: using %s\n", fd->fd_devname,
fwiso_interface[i]->sc1394_dev.dv_xname);
break;
}
}
}
fd->fd_overflow = 0;
return 0;
}
int
fwisoclose(dev_t dev, int flags, int fmt, struct proc *p)
{
struct fwiso_data *fd;
int unit = UNIT(dev);
if (unit >= NFWISO) {
return ENXIO;
}
fd = &fwiso_data_str[unit];
fd->fd_flags &= ~FWISO_OPEN;
/* remove interrupt handler */
if (fd->fd_flags & FWISO_SETHANDLER) {
int s = splbio();
/* remove handler */
(*fd->fd_dev->sc1394_ir_close)((struct device *)fd->fd_dev,
fd->fd_irtag);
fd->fd_flags &= ~FWISO_SETHANDLER;
splx(s);
}
/* When this is opened for write, cleanup it_tag */
if (fd->fd_flags & FWISO_WRITE) {
fwohci_it_ctx_clear(fd->fd_ittag);
fd->fd_ittag = NULL;
}
if (fd->fd_overflow > 0) {
printf("%s: %d frame dropped\n", fd->fd_devname, fd->fd_overflow);
}
fd->fd_flags = 0;
fd->fd_overflow = 0;
/* keep mode and interface */
return 0;
}
int
fwisoioctl(dev_t dev, u_long cmd, caddr_t data, int fflag, struct proc *p)
{
int unit = UNIT(dev);
struct fwiso_data *fd;
int error = 0;
if (unit >= NFWISO) {
return ENXIO;
}
fd = &fwiso_data_str[unit];
if (!data) {
return EINVAL;
}
switch(cmd) {
case FWISOSETIF:
{
const struct fwiso_if *fi = (struct fwiso_if *)data;
if ((fd->fd_dev = fwiso_lookup_if(fi->fi_name)) == NULL) {
error = EINVAL;
}
break;
}
case FWISOGETIF:
{
struct fwiso_if *fi = (struct fwiso_if *)data;
if (fd->fd_dev != NULL) {
memcpy(fi->fi_name, fd->fd_dev->sc1394_dev.dv_xname,
FWISO_IFNAMESIZ);
} else {
memcpy(fi->fi_name, "---", 3);
}
break;
}
case FWISOSETMODE:
{
const int mode = *(int *)data;
if (mode >= 0 && mode < FWISO_MODE_MAX) {
fd->fd_mode = mode;
} else {
error = EINVAL;
}
break;
}
case FWISOGETMODE:
*((int *)data) = fd->fd_mode;
break;
case FWISOSETCHANNEL:
{
const int channel = *(int *)data;
if (channel >= 0 && channel < 64) {
fd->fd_channel = channel;
} else if (channel == FWISO_CHANNEL_ANY) {
fd->fd_channel = IEEE1394_ISO_CHANNEL_ANY;
} else {
error = EINVAL;
}
break;
}
case FWISOGETCHANNEL:
*((int *)data) = fd->fd_channel;
break;
case FWISOSETTAG:
{
const int tag = *(int *)data;
#define FWISO_TAG_MAX (FWISO_TAG0 | FWISO_TAG1 | FWISO_TAG2 | FWISO_TAG3)
if (tag >= FWISO_TAG0 || tag <= FWISO_TAG_MAX) {
/* valid tag */
fd->fd_tag = tag;
} else {
error = EINVAL;
}
}
case FWISOGETTAG:
*((int *)data) = fd->fd_tag;
break;
default:
error = EINVAL;
break;
}
return error;
}
int
fwisopoll(dev_t dev, int events, struct proc *p)
{
int unit = UNIT(dev);
struct fwiso_data *fd;
int s;
int revents;
int (*ir_select)(struct device *, ieee1394_ir_tag_t, struct proc *);
if (unit >= NFWISO) {
return ENXIO;
}
fd = &fwiso_data_str[unit];
if ((ir_select = fd->fd_dev->sc1394_ir_select) == NULL) {
return events;
}
s = splbio();
/*
* right now, only select for read is supported.
*/
revents = events & (POLLOUT | POLLWRNORM);
if (events & (POLLIN | POLLRDNORM)) {
if ((fd->fd_flags & FWISO_SETHANDLER) == 0) {
int errcode;
if ((errcode = fwiso_set_handler(fd)) != 0) {
splx(s);
return errcode;
}
}
if ((*ir_select)((struct device *)fd->fd_dev,
fd->fd_irtag, p) > 0) {
revents |= events & (POLLIN | POLLRDNORM);
}
#if 0
if (fd->fd_rsv_pkts.fp_iovcnt > 0) {
revents |= events & (POLLIN | POLLRDNORM);
} else {
selrecord(p, &fd->fd_sel);
}
#endif
}
splx(s);
return revents;
}
int
fwiso_register_if(struct ieee1394_softc *interface)
{
int i;
for (i = 0; i < FWISO_MAX_INTERFACE; ++i) {
if (fwiso_interface[i] == NULL) {
fwiso_interface[i] = interface;
break;
}
}
if (i == FWISO_MAX_INTERFACE) {
printf("fwiso: too many interfaces %s\n",
interface->sc1394_dev.dv_xname);
return 1;
}
return 0;
}
static struct ieee1394_softc *
fwiso_lookup_if(const char *ifname)
{
int i;
for (i = 0; i < FWISO_MAX_INTERFACE; ++i) {
if (!strncmp(fwiso_interface[i]->sc1394_dev.dv_xname,
ifname, FWISO_IFNAMESIZ)) {
return fwiso_interface[i];
}
}
return NULL;
}
/*
* static int fwiso_set_handler(struct fwiso_data *fd)
*
* This function set interrupt handler for isochronous data read.
*/
static int
fwiso_set_handler(struct fwiso_data *fd)
{
int bufsize;
int s;
struct ieee1394_softc *isc = fd->fd_dev;
int flags = 0;
if (isc == NULL || isc->sc1394_ir_open == NULL) {
return ENXIO;
}
switch (fd->fd_mode) {
case FWISO_MODE_DV:
if (fd->fd_threshold == 0) {
fd->fd_threshold = 30;
}
fd->fd_rsv_size = 1024; /* buffer for 256 ms DV data */
bufsize = 488;
flags |= IEEE1394_IR_TRIGGER_CIP_SYNC;
break;
case FWISO_MODE_DV_RAW:
fd->fd_rsv_size = 128; /* buffer for 16 ms DV data */
bufsize = 488;
flags |= IEEE1394_IR_SHORTDELAY;
break;
case FWISO_MODE_RAW:
fd->fd_rsv_size = 64;
bufsize = 4000;
flags |= IEEE1394_IR_NEEDHEADER | IEEE1394_IR_SHORTDELAY;
break;
case FWISO_MODE_MPEG2TS:
fd->fd_rsv_size = 64;
bufsize = 512;
break;
}
s = splbio();
/* set interrupt handler */
fd->fd_irtag = (*isc->sc1394_ir_open)((struct device *)isc,
fd->fd_channel, fd->fd_tag, fd->fd_rsv_size, bufsize, flags);
if (fd->fd_irtag == NULL) {
splx(s);
return ENXIO;
}
fd->fd_flags |= FWISO_SETHANDLER;
splx(s);
return 0;
}