2024-02-18 08:31:47 +01:00
|
|
|
/****************************************************************************
|
|
|
|
* drivers/rptun/rptun_ivshmem.c
|
|
|
|
*
|
|
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
|
|
* this work for additional information regarding copyright ownership. The
|
|
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
|
|
* "License"); you may not use this file except in compliance with the
|
|
|
|
* License. You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
|
|
* License for the specific language governing permissions and limitations
|
|
|
|
* under the License.
|
|
|
|
*
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
* Included Files
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
|
|
|
|
#include <debug.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
|
|
#include <nuttx/drivers/addrenv.h>
|
2024-05-13 11:00:51 +02:00
|
|
|
#include <nuttx/pci/pci_ivshmem.h>
|
2024-02-18 08:31:47 +01:00
|
|
|
#include <nuttx/rptun/rptun.h>
|
|
|
|
#include <nuttx/rptun/rptun_ivshmem.h>
|
|
|
|
#include <nuttx/wqueue.h>
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
* Pre-processor Definitions
|
|
|
|
****************************************************************************/
|
|
|
|
|
2024-05-13 11:00:51 +02:00
|
|
|
#define rptun_ivshmem_from_ivdev(dev) \
|
|
|
|
container_of(ivshmem_get_driver(dev), struct rptun_ivshmem_dev_s, drv)
|
|
|
|
|
2024-02-18 08:31:47 +01:00
|
|
|
#define RPTUN_IVSHMEM_SHMEM_BAR 2
|
|
|
|
#define RPTUN_IVSHMEM_READY 1
|
2024-05-13 11:00:51 +02:00
|
|
|
#define RPTUN_IVSHMEM_WORK_DELAY MSEC2TICK(1)
|
2024-02-18 08:31:47 +01:00
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
* Private Types
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
struct rptun_ivshmem_mem_s
|
|
|
|
{
|
|
|
|
volatile uint64_t basem;
|
|
|
|
volatile uint32_t seqs;
|
|
|
|
volatile uint32_t seqm;
|
|
|
|
volatile uint32_t cmds;
|
|
|
|
volatile uint32_t cmdm;
|
|
|
|
volatile uint32_t reserved;
|
|
|
|
volatile uint32_t rsc_size;
|
|
|
|
struct rptun_rsc_s rsc;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct rptun_ivshmem_dev_s
|
|
|
|
{
|
|
|
|
struct rptun_dev_s rptun;
|
2024-05-13 11:00:51 +02:00
|
|
|
struct ivshmem_driver_s drv;
|
2024-02-18 08:31:47 +01:00
|
|
|
rptun_callback_t callback;
|
|
|
|
FAR void *arg;
|
|
|
|
uint32_t seq;
|
|
|
|
FAR struct rptun_ivshmem_mem_s *shmem;
|
|
|
|
size_t shmem_size;
|
2024-05-16 09:32:01 +02:00
|
|
|
struct simple_addrenv_s addrenv[2];
|
|
|
|
struct rptun_addrenv_s raddrenv[2];
|
2024-02-18 08:31:47 +01:00
|
|
|
bool master;
|
|
|
|
char cpuname[RPMSG_NAME_SIZE + 1];
|
2024-05-13 11:00:51 +02:00
|
|
|
FAR struct ivshmem_device_s *ivdev;
|
2024-02-18 08:31:47 +01:00
|
|
|
|
|
|
|
/* Work queue for transmit */
|
|
|
|
|
|
|
|
struct work_s worker;
|
|
|
|
};
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
* Private Function Prototypes
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
static const char *rptun_ivshmem_get_cpuname(FAR struct rptun_dev_s *dev);
|
|
|
|
static const FAR struct rptun_addrenv_s *
|
|
|
|
rptun_ivshmem_get_addrenv(FAR struct rptun_dev_s *dev);
|
|
|
|
static FAR struct rptun_rsc_s *
|
|
|
|
rptun_ivshmem_get_resource(FAR struct rptun_dev_s *dev);
|
|
|
|
static bool rptun_ivshmem_is_autostart(FAR struct rptun_dev_s *dev);
|
|
|
|
static bool rptun_ivshmem_is_master(FAR struct rptun_dev_s *dev);
|
|
|
|
static int rptun_ivshmem_start(FAR struct rptun_dev_s *dev);
|
|
|
|
static int rptun_ivshmem_stop(FAR struct rptun_dev_s *dev);
|
|
|
|
static int rptun_ivshmem_notify(FAR struct rptun_dev_s *dev,
|
|
|
|
uint32_t notifyid);
|
|
|
|
static int rptun_ivshmem_register_callback(FAR struct rptun_dev_s *dev,
|
|
|
|
rptun_callback_t callback,
|
|
|
|
FAR void *arg);
|
|
|
|
|
|
|
|
static void rptun_ivshmem_work(FAR void *arg);
|
2024-05-13 11:00:51 +02:00
|
|
|
static int rptun_ivshmem_probe(FAR struct ivshmem_device_s *dev);
|
|
|
|
static void rptun_ivshmem_remove(FAR struct ivshmem_device_s *dev);
|
2024-02-18 08:31:47 +01:00
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
* Private Data
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
static const struct rptun_ops_s g_rptun_ivshmem_ops =
|
|
|
|
{
|
|
|
|
.get_cpuname = rptun_ivshmem_get_cpuname,
|
|
|
|
.get_addrenv = rptun_ivshmem_get_addrenv,
|
|
|
|
.get_resource = rptun_ivshmem_get_resource,
|
|
|
|
.is_autostart = rptun_ivshmem_is_autostart,
|
|
|
|
.is_master = rptun_ivshmem_is_master,
|
|
|
|
.start = rptun_ivshmem_start,
|
|
|
|
.stop = rptun_ivshmem_stop,
|
|
|
|
.notify = rptun_ivshmem_notify,
|
|
|
|
.register_callback = rptun_ivshmem_register_callback,
|
|
|
|
};
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
* Private Functions
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
static const FAR char *rptun_ivshmem_get_cpuname(FAR struct rptun_dev_s *dev)
|
|
|
|
{
|
|
|
|
FAR struct rptun_ivshmem_dev_s *priv =
|
|
|
|
(FAR struct rptun_ivshmem_dev_s *)dev;
|
|
|
|
return priv->cpuname;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const FAR struct rptun_addrenv_s *
|
|
|
|
rptun_ivshmem_get_addrenv(FAR struct rptun_dev_s *dev)
|
|
|
|
{
|
|
|
|
FAR struct rptun_ivshmem_dev_s *priv =
|
|
|
|
(FAR struct rptun_ivshmem_dev_s *)dev;
|
2024-05-16 09:32:01 +02:00
|
|
|
return &priv->raddrenv[0];
|
2024-02-18 08:31:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static FAR struct rptun_rsc_s *
|
|
|
|
rptun_ivshmem_get_resource(FAR struct rptun_dev_s *dev)
|
|
|
|
{
|
|
|
|
FAR struct rptun_ivshmem_dev_s *priv =
|
|
|
|
(FAR struct rptun_ivshmem_dev_s *)dev;
|
|
|
|
|
2024-05-16 09:32:01 +02:00
|
|
|
priv->raddrenv[0].da = 0;
|
|
|
|
priv->raddrenv[0].size = priv->shmem_size;
|
2024-02-18 08:31:47 +01:00
|
|
|
|
|
|
|
if (priv->master)
|
|
|
|
{
|
2024-05-16 09:32:01 +02:00
|
|
|
priv->raddrenv[0].pa = (uintptr_t)priv->shmem;
|
2024-05-13 11:00:51 +02:00
|
|
|
|
2024-02-18 08:31:47 +01:00
|
|
|
/* Wait untils salve is ready */
|
|
|
|
|
|
|
|
while (priv->shmem->cmds != RPTUN_IVSHMEM_READY)
|
|
|
|
{
|
|
|
|
usleep(1000);
|
|
|
|
}
|
|
|
|
|
|
|
|
priv->shmem->cmds = 0;
|
2024-02-26 14:08:38 +01:00
|
|
|
priv->shmem->basem = (uint64_t)(uintptr_t)priv->shmem;
|
2024-02-18 08:31:47 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
FAR struct rptun_rsc_s *rsc = &priv->shmem->rsc;
|
|
|
|
|
|
|
|
memset(priv->shmem, 0, priv->shmem_size);
|
|
|
|
|
|
|
|
rsc->rsc_tbl_hdr.ver = 1;
|
|
|
|
rsc->rsc_tbl_hdr.num = 1;
|
|
|
|
rsc->offset[0] = offsetof(struct rptun_rsc_s,
|
|
|
|
rpmsg_vdev);
|
|
|
|
rsc->rpmsg_vdev.type = RSC_VDEV;
|
|
|
|
rsc->rpmsg_vdev.id = VIRTIO_ID_RPMSG;
|
|
|
|
rsc->rpmsg_vdev.notifyid = 20;
|
|
|
|
rsc->rpmsg_vdev.dfeatures = 1 << VIRTIO_RPMSG_F_NS |
|
|
|
|
1 << VIRTIO_RPMSG_F_ACK |
|
|
|
|
1 << VIRTIO_RPMSG_F_BUFSZ;
|
|
|
|
rsc->rpmsg_vdev.config_len = sizeof(struct fw_rsc_config);
|
|
|
|
rsc->rpmsg_vdev.num_of_vrings = 2;
|
|
|
|
rsc->rpmsg_vring0.da = FW_RSC_U32_ADDR_ANY;
|
|
|
|
rsc->rpmsg_vring0.align = 8;
|
|
|
|
rsc->rpmsg_vring0.num = CONFIG_RPTUN_IVSHMEM_BUFFNUM;
|
|
|
|
rsc->rpmsg_vring0.notifyid = RSC_NOTIFY_ID_ANY;
|
|
|
|
rsc->rpmsg_vring1.da = FW_RSC_U32_ADDR_ANY;
|
|
|
|
rsc->rpmsg_vring1.align = 8;
|
|
|
|
rsc->rpmsg_vring1.num = CONFIG_RPTUN_IVSHMEM_BUFFNUM;
|
|
|
|
rsc->rpmsg_vring1.notifyid = RSC_NOTIFY_ID_ANY;
|
|
|
|
rsc->config.r2h_buf_size = CONFIG_RPTUN_IVSHMEM_BUFFSIZE;
|
|
|
|
rsc->config.h2r_buf_size = CONFIG_RPTUN_IVSHMEM_BUFFSIZE;
|
|
|
|
|
|
|
|
priv->shmem->rsc_size = sizeof(struct rptun_rsc_s);
|
|
|
|
priv->shmem->cmds = RPTUN_IVSHMEM_READY;
|
|
|
|
|
|
|
|
/* Wait untils master is ready, salve need use master base to
|
|
|
|
* initialize addrenv.
|
|
|
|
*/
|
|
|
|
|
|
|
|
while (priv->shmem->basem == 0)
|
|
|
|
{
|
|
|
|
usleep(1000);
|
|
|
|
}
|
|
|
|
|
2024-05-16 09:32:01 +02:00
|
|
|
priv->raddrenv[0].pa = (uintptr_t)priv->shmem->basem;
|
2024-05-13 11:00:51 +02:00
|
|
|
|
2024-05-16 09:32:01 +02:00
|
|
|
priv->addrenv[0].va = (uint64_t)(uintptr_t)priv->shmem;
|
|
|
|
priv->addrenv[0].pa = priv->shmem->basem;
|
|
|
|
priv->addrenv[0].size = priv->shmem_size;
|
2024-02-18 08:31:47 +01:00
|
|
|
|
2024-05-16 09:32:01 +02:00
|
|
|
simple_addrenv_initialize(&priv->addrenv[0]);
|
2024-02-18 08:31:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return &priv->shmem->rsc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool rptun_ivshmem_is_autostart(FAR struct rptun_dev_s *dev)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool rptun_ivshmem_is_master(FAR struct rptun_dev_s *dev)
|
|
|
|
{
|
|
|
|
FAR struct rptun_ivshmem_dev_s *priv =
|
|
|
|
(FAR struct rptun_ivshmem_dev_s *)dev;
|
|
|
|
return priv->master;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int rptun_ivshmem_start(FAR struct rptun_dev_s *dev)
|
|
|
|
{
|
|
|
|
FAR struct rptun_ivshmem_dev_s *priv =
|
|
|
|
(FAR struct rptun_ivshmem_dev_s *)dev;
|
2024-05-13 11:00:51 +02:00
|
|
|
|
|
|
|
if (ivshmem_support_irq(priv->ivdev))
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-02-18 08:31:47 +01:00
|
|
|
return work_queue(HPWORK, &priv->worker, rptun_ivshmem_work, priv, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int rptun_ivshmem_stop(FAR struct rptun_dev_s *dev)
|
|
|
|
{
|
|
|
|
FAR struct rptun_ivshmem_dev_s *priv =
|
|
|
|
(FAR struct rptun_ivshmem_dev_s *)dev;
|
2024-05-13 11:00:51 +02:00
|
|
|
|
|
|
|
if (ivshmem_support_irq(priv->ivdev))
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-02-18 08:31:47 +01:00
|
|
|
return work_cancel_sync(HPWORK, &priv->worker);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int rptun_ivshmem_notify(FAR struct rptun_dev_s *dev, uint32_t vqid)
|
|
|
|
{
|
|
|
|
FAR struct rptun_ivshmem_dev_s *priv =
|
|
|
|
(FAR struct rptun_ivshmem_dev_s *)dev;
|
|
|
|
|
2024-05-13 11:00:51 +02:00
|
|
|
if (ivshmem_support_irq(priv->ivdev))
|
|
|
|
{
|
|
|
|
ivshmem_kick_peer(priv->ivdev);
|
|
|
|
}
|
|
|
|
else if (priv->master)
|
2024-02-18 08:31:47 +01:00
|
|
|
{
|
|
|
|
priv->shmem->seqm++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
priv->shmem->seqs++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int rptun_ivshmem_register_callback(FAR struct rptun_dev_s *dev,
|
|
|
|
rptun_callback_t callback,
|
|
|
|
FAR void *arg)
|
|
|
|
{
|
|
|
|
FAR struct rptun_ivshmem_dev_s *priv =
|
|
|
|
(FAR struct rptun_ivshmem_dev_s *)dev;
|
|
|
|
|
|
|
|
priv->callback = callback;
|
|
|
|
priv->arg = arg;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-05-13 11:00:51 +02:00
|
|
|
/****************************************************************************
|
|
|
|
* Name: rptun_ivshmem_interrupt
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
static int rptun_ivshmem_interrupt(int irq, FAR void *context, FAR void *arg)
|
|
|
|
{
|
|
|
|
FAR struct rptun_ivshmem_dev_s *priv = arg;
|
|
|
|
|
|
|
|
if (priv->callback != NULL)
|
|
|
|
{
|
|
|
|
priv->callback(priv->arg, RPTUN_NOTIFY_ALL);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-02-18 08:31:47 +01:00
|
|
|
/****************************************************************************
|
|
|
|
* Name: rptun_ivshmem_work
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
static void rptun_ivshmem_work(FAR void *arg)
|
|
|
|
{
|
|
|
|
FAR struct rptun_ivshmem_dev_s *priv = arg;
|
|
|
|
bool should_notify = false;
|
|
|
|
|
|
|
|
if (priv->master && priv->seq != priv->shmem->seqs)
|
|
|
|
{
|
|
|
|
priv->seq = priv->shmem->seqs;
|
|
|
|
should_notify = true;
|
|
|
|
}
|
|
|
|
else if (!priv->master && priv->seq != priv->shmem->seqm)
|
|
|
|
{
|
|
|
|
priv->seq = priv->shmem->seqm;
|
|
|
|
should_notify = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (should_notify && priv->callback != NULL)
|
|
|
|
{
|
|
|
|
priv->callback(priv->arg, RPTUN_NOTIFY_ALL);
|
|
|
|
}
|
|
|
|
|
|
|
|
work_queue(HPWORK, &priv->worker, rptun_ivshmem_work, priv,
|
|
|
|
RPTUN_IVSHMEM_WORK_DELAY);
|
|
|
|
}
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
* Name: rptun_ivshmem_probe
|
|
|
|
****************************************************************************/
|
|
|
|
|
2024-05-13 11:00:51 +02:00
|
|
|
static int rptun_ivshmem_probe(FAR struct ivshmem_device_s *ivdev)
|
2024-02-18 08:31:47 +01:00
|
|
|
{
|
2024-05-13 11:00:51 +02:00
|
|
|
FAR struct rptun_ivshmem_dev_s *priv = rptun_ivshmem_from_ivdev(ivdev);
|
2024-02-18 08:31:47 +01:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
/* Do the rptun ivshmem init */
|
|
|
|
|
|
|
|
priv->rptun.ops = &g_rptun_ivshmem_ops;
|
2024-05-13 11:00:51 +02:00
|
|
|
priv->ivdev = ivdev;
|
|
|
|
priv->shmem = ivshmem_get_shmem(ivdev, &priv->shmem_size);
|
2024-02-18 08:31:47 +01:00
|
|
|
|
2024-05-13 11:00:51 +02:00
|
|
|
ivshmem_attach_irq(ivdev, rptun_ivshmem_interrupt, priv);
|
|
|
|
ivshmem_control_irq(ivdev, true);
|
2024-02-18 08:31:47 +01:00
|
|
|
|
|
|
|
pciinfo("shmem addr=%p size=%zu\n", priv->shmem, priv->shmem_size);
|
|
|
|
|
|
|
|
/* Do rptun initialize */
|
|
|
|
|
|
|
|
ret = rptun_initialize(&priv->rptun);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
pcierr("rptun intialize failed, ret=%d\n", ret);
|
2024-05-13 11:00:51 +02:00
|
|
|
goto err;
|
2024-02-18 08:31:47 +01:00
|
|
|
}
|
|
|
|
|
2024-05-13 11:00:51 +02:00
|
|
|
if (!priv->master && !ivshmem_support_irq(ivdev))
|
2024-02-18 08:31:47 +01:00
|
|
|
{
|
2024-05-13 11:00:51 +02:00
|
|
|
pciinfo("queue the worker\n");
|
|
|
|
|
2024-02-18 08:31:47 +01:00
|
|
|
/* Queue the worker for slave, master will do this in ops->start() */
|
|
|
|
|
|
|
|
work_queue(HPWORK, &priv->worker, rptun_ivshmem_work, priv, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
2024-05-13 11:00:51 +02:00
|
|
|
err:
|
|
|
|
ivshmem_unregister_driver(&priv->drv);
|
|
|
|
ivshmem_control_irq(ivdev, false);
|
|
|
|
ivshmem_detach_irq(ivdev);
|
2024-02-18 08:31:47 +01:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
* Name: rptun_ivshmem_remove
|
|
|
|
****************************************************************************/
|
|
|
|
|
2024-05-13 11:00:51 +02:00
|
|
|
static void rptun_ivshmem_remove(FAR struct ivshmem_device_s *ivdev)
|
2024-02-18 08:31:47 +01:00
|
|
|
{
|
2024-05-13 11:00:51 +02:00
|
|
|
FAR struct rptun_ivshmem_dev_s *priv = rptun_ivshmem_from_ivdev(ivdev);
|
|
|
|
|
|
|
|
ivshmem_unregister_driver(&priv->drv);
|
|
|
|
ivshmem_control_irq(ivdev, false);
|
|
|
|
kmm_free(priv);
|
2024-02-18 08:31:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
* Public Functions
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
int pci_register_rptun_ivshmem_driver(void)
|
|
|
|
{
|
2024-05-13 11:00:51 +02:00
|
|
|
FAR struct rptun_ivshmem_dev_s *priv;
|
|
|
|
FAR const char *str;
|
|
|
|
FAR char *name = CONFIG_RPTUN_IVSHMEM_NAME;
|
|
|
|
|
|
|
|
while (name != NULL)
|
|
|
|
{
|
|
|
|
priv = kmm_zalloc(sizeof(*priv));
|
|
|
|
if (priv == NULL)
|
|
|
|
{
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
priv->drv.id = strtoul(name, &name, 0);
|
|
|
|
str = strchr(++name, ':');
|
|
|
|
snprintf(priv->cpuname, RPMSG_NAME_SIZE, "%.*s", (int)(str - name),
|
|
|
|
name);
|
|
|
|
priv->master = *(str + 1) == 'm';
|
|
|
|
|
|
|
|
pciinfo("Register ivshmem driver, id=%d, cpuname=%s, master=%d\n",
|
|
|
|
priv->drv.id, priv->cpuname, priv->master);
|
|
|
|
|
|
|
|
priv->drv.probe = rptun_ivshmem_probe;
|
|
|
|
priv->drv.remove = rptun_ivshmem_remove;
|
|
|
|
if (ivshmem_register_driver(&priv->drv) < 0)
|
|
|
|
{
|
|
|
|
kmm_free(priv);
|
|
|
|
}
|
|
|
|
|
|
|
|
name = strchr(str, ';');
|
|
|
|
if (name != NULL)
|
|
|
|
{
|
|
|
|
name++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2024-02-18 08:31:47 +01:00
|
|
|
}
|
|
|
|
|