mutex_lock(&mhi_cntrl->event_lock);
ring = &mhi_cntrl->mhi_event[ring_idx].ring;
ctx = (union mhi_ep_ring_ctx *)&mhi_cntrl->ev_ctx_cache[ring_idx]; if (!ring->started) {
ret = mhi_ep_ring_start(mhi_cntrl, ring, ctx); if (ret) {
dev_err(dev, "Error starting event ring (%u)\n", ring_idx); goto err_unlock;
}
}
/* Add element to the event ring */
ret = mhi_ep_ring_add_element(ring, el); if (ret) {
dev_err(dev, "Error adding element to event ring (%u)\n", ring_idx); goto err_unlock;
}
mutex_unlock(&mhi_cntrl->event_lock);
/* *AspertheMHIspecification,section4.3,Interruptmoderation: * *1.IfBEIflagisnotset,cancelanypendingintmodtworkifstarted *fortheeventringandraiseIRQimmediately. * *2.IfbothBEIandintmodtareset,andifnoIRQispendingforthe *sameeventring,starttheIRQdelayedworkasperthevalueof *intmodt.IfpreviousIRQispending,thendonothingasthepending *IRQisenoughforthehosttoprocessthecurrenteventringelement. * *3.IfBEIissetandintmodtisnotset,noneedtoraiseIRQ.
*/ if (!bei) { if (READ_ONCE(ring->irq_pending))
cancel_delayed_work(&ring->intmodt_work);
/* Check if the channel is supported by the controller */ if ((ch_id >= mhi_cntrl->max_chan) || !mhi_cntrl->mhi_chan[ch_id].name) {
dev_dbg(dev, "Channel (%u) not supported!\n", ch_id); return -ENODEV;
}
switch (MHI_TRE_GET_CMD_TYPE(el)) { case MHI_PKT_TYPE_START_CHAN_CMD:
dev_dbg(dev, "Received START command for channel (%u)\n", ch_id);
mutex_lock(&mhi_chan->lock); /* Initialize and configure the corresponding channel ring */ if (!ch_ring->started) {
ret = mhi_ep_ring_start(mhi_cntrl, ch_ring,
(union mhi_ep_ring_ctx *)&mhi_cntrl->ch_ctx_cache[ch_id]); if (ret) {
dev_err(dev, "Failed to start ring for channel (%u)\n", ch_id);
ret = mhi_ep_send_cmd_comp_event(mhi_cntrl,
MHI_EV_CC_UNDEFINED_ERR); if (ret)
dev_err(dev, "Error sending completion event: %d\n", ret);
goto err_unlock;
}
mhi_chan->rd_offset = ch_ring->rd_offset;
}
/* Set channel state to RUNNING */
mhi_chan->state = MHI_CH_STATE_RUNNING;
tmp = le32_to_cpu(mhi_cntrl->ch_ctx_cache[ch_id].chcfg);
tmp &= ~CHAN_CTX_CHSTATE_MASK;
tmp |= FIELD_PREP(CHAN_CTX_CHSTATE_MASK, MHI_CH_STATE_RUNNING);
mhi_cntrl->ch_ctx_cache[ch_id].chcfg = cpu_to_le32(tmp);
ret = mhi_ep_send_cmd_comp_event(mhi_cntrl, MHI_EV_CC_SUCCESS); if (ret) {
dev_err(dev, "Error sending command completion event (%u)\n",
MHI_EV_CC_SUCCESS); goto err_unlock;
}
mutex_unlock(&mhi_chan->lock);
/* *CreateMHIdeviceonlyduringULchannelstart.SincetheMHI *channelsoperateinapair,we'llassociatebothULandDL *channelstothesamedevice. * *Wealsoneedtocheckformhi_dev!=NULLbecause,thehost *willissueSTART_CHANcommandduringresumeandwedon't *destroythedeviceduringsuspend.
*/ if (!(ch_id % 2) && !mhi_chan->mhi_dev) {
ret = mhi_ep_create_device(mhi_cntrl, ch_id); if (ret) {
dev_err(dev, "Error creating device for channel (%u)\n", ch_id);
mhi_ep_handle_syserr(mhi_cntrl); return ret;
}
}
/* Finally, enable DB for the channel */
mhi_ep_mmio_enable_chdb(mhi_cntrl, ch_id);
break; case MHI_PKT_TYPE_STOP_CHAN_CMD:
dev_dbg(dev, "Received STOP command for channel (%u)\n", ch_id); if (!ch_ring->started) {
dev_err(dev, "Channel (%u) not opened\n", ch_id); return -ENODEV;
}
mutex_lock(&mhi_chan->lock); /* Disable DB for the channel */
mhi_ep_mmio_disable_chdb(mhi_cntrl, ch_id);
/* Send channel disconnect status to client drivers */ if (mhi_chan->xfer_cb) {
result.transaction_status = -ENOTCONN;
result.bytes_xferd = 0;
mhi_chan->xfer_cb(mhi_chan->mhi_dev, &result);
}
/* Set channel state to STOP */
mhi_chan->state = MHI_CH_STATE_STOP;
tmp = le32_to_cpu(mhi_cntrl->ch_ctx_cache[ch_id].chcfg);
tmp &= ~CHAN_CTX_CHSTATE_MASK;
tmp |= FIELD_PREP(CHAN_CTX_CHSTATE_MASK, MHI_CH_STATE_STOP);
mhi_cntrl->ch_ctx_cache[ch_id].chcfg = cpu_to_le32(tmp);
ret = mhi_ep_send_cmd_comp_event(mhi_cntrl, MHI_EV_CC_SUCCESS); if (ret) {
dev_err(dev, "Error sending command completion event (%u)\n",
MHI_EV_CC_SUCCESS); goto err_unlock;
}
mutex_unlock(&mhi_chan->lock); break; case MHI_PKT_TYPE_RESET_CHAN_CMD:
dev_dbg(dev, "Received RESET command for channel (%u)\n", ch_id); if (!ch_ring->started) {
dev_err(dev, "Channel (%u) not opened\n", ch_id); return -ENODEV;
}
mutex_lock(&mhi_chan->lock); /* Stop and reset the transfer ring */
mhi_ep_ring_reset(mhi_cntrl, ch_ring);
/* Send channel disconnect status to client driver */ if (mhi_chan->xfer_cb) {
result.transaction_status = -ENOTCONN;
result.bytes_xferd = 0;
mhi_chan->xfer_cb(mhi_chan->mhi_dev, &result);
}
/* Set channel state to DISABLED */
mhi_chan->state = MHI_CH_STATE_DISABLED;
tmp = le32_to_cpu(mhi_cntrl->ch_ctx_cache[ch_id].chcfg);
tmp &= ~CHAN_CTX_CHSTATE_MASK;
tmp |= FIELD_PREP(CHAN_CTX_CHSTATE_MASK, MHI_CH_STATE_DISABLED);
mhi_cntrl->ch_ctx_cache[ch_id].chcfg = cpu_to_le32(tmp);
ret = mhi_ep_send_cmd_comp_event(mhi_cntrl, MHI_EV_CC_SUCCESS); if (ret) {
dev_err(dev, "Error sending command completion event (%u)\n",
MHI_EV_CC_SUCCESS); goto err_unlock;
}
do { /* Don't process the transfer ring if the channel is not in RUNNING state */ if (mhi_chan->state != MHI_CH_STATE_RUNNING) {
dev_err(dev, "Channel not available\n"); return -ENODEV;
}
el = &ring->ring_cache[mhi_chan->rd_offset];
/* Check if there is data pending to be read from previous read operation */ if (mhi_chan->tre_bytes_left) {
dev_dbg(dev, "TRE bytes remaining: %u\n", mhi_chan->tre_bytes_left);
tr_len = min(len, mhi_chan->tre_bytes_left);
} else {
mhi_chan->tre_loc = MHI_TRE_DATA_GET_PTR(el);
mhi_chan->tre_size = MHI_TRE_DATA_GET_LEN(el);
mhi_chan->tre_bytes_left = mhi_chan->tre_size;
if (mhi_chan->tre_bytes_left - tr_len)
buf_info.code = MHI_EV_CC_OVERFLOW;
dev_dbg(dev, "Reading %zd bytes from channel (%u)\n", tr_len, ring->ch_id);
ret = mhi_cntrl->read_async(mhi_cntrl, &buf_info); if (ret < 0) {
dev_err(&mhi_chan->mhi_dev->dev, "Error reading from channel\n"); goto err_free_buf_addr;
}
mhi_chan->tre_bytes_left -= tr_len;
if (!mhi_chan->tre_bytes_left)
mhi_chan->rd_offset = (mhi_chan->rd_offset + 1) % ring->ring_size; /* Read until the some buffer is left or the ring becomes not empty */
} while (!mhi_ep_queue_is_empty(mhi_chan->mhi_dev, DMA_TO_DEVICE));
ret = mhi_ep_send_completion_event(mhi_cntrl, ring, el, buf_info->size,
buf_info->code); if (ret) {
dev_err(dev, "Error sending transfer completion event\n"); return;
}
buf_left = skb->len;
ring = &mhi_cntrl->mhi_chan[mhi_chan->chan].ring;
mutex_lock(&mhi_chan->lock);
do { /* Don't process the transfer ring if the channel is not in RUNNING state */ if (mhi_chan->state != MHI_CH_STATE_RUNNING) {
dev_err(dev, "Channel not available\n");
ret = -ENODEV; goto err_exit;
}
if (mhi_ep_queue_is_empty(mhi_dev, DMA_FROM_DEVICE)) {
dev_err(dev, "TRE not available!\n");
ret = -ENOSPC; goto err_exit;
}
el = &ring->ring_cache[mhi_chan->rd_offset];
tre_len = MHI_TRE_DATA_GET_LEN(el);
/* Get the channel context base pointer from host */
mhi_ep_mmio_get_chc_base(mhi_cntrl);
/* Allocate and map memory for caching host channel context */
ret = mhi_cntrl->alloc_map(mhi_cntrl, mhi_cntrl->ch_ctx_host_pa,
&mhi_cntrl->ch_ctx_cache_phys,
(void __iomem **) &mhi_cntrl->ch_ctx_cache,
ch_ctx_host_size); if (ret) {
dev_err(dev, "Failed to allocate and map ch_ctx_cache\n"); return ret;
}
/* Get the event context base pointer from host */
mhi_ep_mmio_get_erc_base(mhi_cntrl);
/* Allocate and map memory for caching host event context */
ret = mhi_cntrl->alloc_map(mhi_cntrl, mhi_cntrl->ev_ctx_host_pa,
&mhi_cntrl->ev_ctx_cache_phys,
(void __iomem **) &mhi_cntrl->ev_ctx_cache,
ev_ctx_host_size); if (ret) {
dev_err(dev, "Failed to allocate and map ev_ctx_cache\n"); goto err_ch_ctx;
}
/* Get the command context base pointer from host */
mhi_ep_mmio_get_crc_base(mhi_cntrl);
/* Allocate and map memory for caching host command context */
ret = mhi_cntrl->alloc_map(mhi_cntrl, mhi_cntrl->cmd_ctx_host_pa,
&mhi_cntrl->cmd_ctx_cache_phys,
(void __iomem **) &mhi_cntrl->cmd_ctx_cache,
cmd_ctx_host_size); if (ret) {
dev_err(dev, "Failed to allocate and map cmd_ctx_cache\n"); goto err_ev_ctx;
}
/* Initialize command ring */
ret = mhi_ep_ring_start(mhi_cntrl, &mhi_cntrl->mhi_cmd->ring,
(union mhi_ep_ring_ctx *)mhi_cntrl->cmd_ctx_cache); if (ret) {
dev_err(dev, "Failed to start the command ring\n"); goto err_cmd_ctx;
}
/* Wait for Host to set the M0 state */ do {
msleep(M0_WAIT_DELAY_MS);
mhi_ep_mmio_get_mhi_state(mhi_cntrl, &state, &mhi_reset); if (mhi_reset) { /* Clear the MHI reset if host is in reset state */
mhi_ep_mmio_clear_reset(mhi_cntrl);
dev_info(dev, "Detected Host reset while waiting for M0\n");
}
count++;
} while (state != MHI_STATE_M0 && count < M0_WAIT_COUNT);
if (state != MHI_STATE_M0) {
dev_err(dev, "Host failed to enter M0\n"); return -ETIMEDOUT;
}
ret = mhi_ep_cache_host_cfg(mhi_cntrl); if (ret) {
dev_err(dev, "Failed to cache host config\n"); return ret;
}
mhi_ep_mmio_set_env(mhi_cntrl, MHI_EE_AMSS);
/* Enable all interrupts now */
mhi_ep_enable_int(mhi_cntrl);
/* Update the write offset for the ring */
ret = mhi_ep_update_wr_offset(ring); if (ret) {
dev_err(dev, "Error updating write offset for ring\n"); return;
}
/* Sanity check to make sure there are elements in the ring */ if (ring->rd_offset == ring->wr_offset) return;
/* *Processcommandringelementtillwriteoffset.Incaseofanerror,justtryto *processnextelement.
*/ while (ring->rd_offset != ring->wr_offset) {
el = &ring->ring_cache[ring->rd_offset];
ret = mhi_ep_process_cmd_ring(ring, el); if (ret && ret != -ENODEV)
dev_err(dev, "Error processing cmd ring element: %zu\n", ring->rd_offset);
/* Process each queued channel ring. In case of an error, just process next element. */
list_for_each_entry_safe(itr, tmp, &head, node) {
list_del(&itr->node);
ring = itr->ring;
/* Update the write offset for the ring */
ret = mhi_ep_update_wr_offset(ring); if (ret) {
dev_err(dev, "Error updating write offset for ring\n");
mutex_unlock(&chan->lock);
kmem_cache_free(mhi_cntrl->ring_item_cache, itr); continue;
}
/* Sanity check to make sure there are elements in the ring */ if (chan->rd_offset == ring->wr_offset) {
mutex_unlock(&chan->lock);
kmem_cache_free(mhi_cntrl->ring_item_cache, itr); continue;
}
dev_dbg(dev, "Processing the ring for channel (%u)\n", ring->ch_id);
ret = mhi_ep_process_ch_ring(ring); if (ret) {
dev_err(dev, "Error processing ring for channel (%u): %d\n",
ring->ch_id, ret);
mutex_unlock(&chan->lock);
kmem_cache_free(mhi_cntrl->ring_item_cache, itr); continue;
}
list_for_each_entry_safe(itr, tmp, &head, node) {
list_del(&itr->node);
dev_dbg(dev, "Handling MHI state transition to %s\n",
mhi_state_str(itr->state));
switch (itr->state) { case MHI_STATE_M0:
ret = mhi_ep_set_m0_state(mhi_cntrl); if (ret)
dev_err(dev, "Failed to transition to M0 state\n"); break; case MHI_STATE_M3:
ret = mhi_ep_set_m3_state(mhi_cntrl); if (ret)
dev_err(dev, "Failed to transition to M3 state\n"); break; default:
dev_err(dev, "Invalid MHI state transition: %d\n", itr->state); break;
}
kfree(itr);
}
}
/* First add the ring items to a local list */
for_each_set_bit(i, &ch_int, 32) { /* Channel index varies for each register: 0, 32, 64, 96 */
u32 ch_id = ch_idx + i;
ring = &mhi_cntrl->mhi_chan[ch_id].ring;
item = kmem_cache_zalloc(mhi_cntrl->ring_item_cache, GFP_ATOMIC); if (!item) return;
/* Now, splice the local list into ch_db_list and queue the work item */ if (work) {
spin_lock(&mhi_cntrl->list_lock);
list_splice_tail_init(&head, &mhi_cntrl->ch_db_list);
spin_unlock(&mhi_cntrl->list_lock);
/* Bail out if there is no channel doorbell interrupt */ if (!mhi_ep_mmio_read_chdb_status_interrupts(mhi_cntrl)) return;
for (i = 0; i < MHI_MASK_ROWS_CH_DB; i++) {
ch_idx = i * MHI_MASK_CH_LEN;
/* Only process channel interrupt if the mask is enabled */
ch_int = mhi_cntrl->chdb[i].status & mhi_cntrl->chdb[i].mask; if (ch_int) {
mhi_ep_queue_channel_db(mhi_cntrl, ch_int, ch_idx);
mhi_ep_mmio_write(mhi_cntrl, MHI_CHDB_INT_CLEAR_n(i),
mhi_cntrl->chdb[i].status);
}
}
}
/* Destroy devices associated with all channels */
device_for_each_child(&mhi_cntrl->mhi_dev->dev, NULL, mhi_ep_destroy_device);
/* Stop and reset the transfer rings */ for (i = 0; i < mhi_cntrl->max_chan; i++) {
mhi_chan = &mhi_cntrl->mhi_chan[i]; if (!mhi_chan->ring.started) continue;
/* Stop and reset the event rings */ for (i = 0; i < mhi_cntrl->event_rings; i++) {
ev_ring = &mhi_cntrl->mhi_event[i].ring; if (!ev_ring->started) continue;
ret = mhi_ep_set_mhi_state(mhi_cntrl, MHI_STATE_SYS_ERR); if (ret) return;
/* Signal host that the device went to SYS_ERR state */
ret = mhi_ep_send_state_change_event(mhi_cntrl, MHI_STATE_SYS_ERR); if (ret)
dev_err(dev, "Failed sending SYS_ERR state change event: %d\n", ret);
}
int mhi_ep_power_up(struct mhi_ep_cntrl *mhi_cntrl)
{ struct device *dev = &mhi_cntrl->mhi_dev->dev; int ret, i;
mhi_cntrl->mhi_event = kcalloc(mhi_cntrl->event_rings, sizeof(*mhi_cntrl->mhi_event),
GFP_KERNEL); if (!mhi_cntrl->mhi_event) return -ENOMEM;
/* Initialize command, channel and event rings */
mhi_ep_ring_init(&mhi_cntrl->mhi_cmd->ring, RING_TYPE_CMD, 0); for (i = 0; i < mhi_cntrl->max_chan; i++)
mhi_ep_ring_init(&mhi_cntrl->mhi_chan[i].ring, RING_TYPE_CH, i); for (i = 0; i < mhi_cntrl->event_rings; i++)
mhi_ep_ring_init(&mhi_cntrl->mhi_event[i].ring, RING_TYPE_ER, i);
mhi_cntrl->mhi_state = MHI_STATE_RESET;
/* Set AMSS EE before signaling ready state */
mhi_ep_mmio_set_env(mhi_cntrl, MHI_EE_AMSS);
/* All set, notify the host that we are ready */
ret = mhi_ep_set_ready_state(mhi_cntrl); if (ret) goto err_free_event;
dev_dbg(dev, "READY state notification sent to the host\n");
ret = mhi_ep_enable(mhi_cntrl); if (ret) {
dev_err(dev, "Failed to enable MHI endpoint\n"); goto err_free_event;
}
for (i = 0; i < mhi_cntrl->max_chan; i++) {
mhi_chan = &mhi_cntrl->mhi_chan[i];
if (!mhi_chan->mhi_dev) continue;
mutex_lock(&mhi_chan->lock); /* Skip if the channel is not currently running */
tmp = le32_to_cpu(mhi_cntrl->ch_ctx_cache[i].chcfg); if (FIELD_GET(CHAN_CTX_CHSTATE_MASK, tmp) != MHI_CH_STATE_RUNNING) {
mutex_unlock(&mhi_chan->lock); continue;
}
dev_dbg(&mhi_chan->mhi_dev->dev, "Suspending channel\n"); /* Set channel state to SUSPENDED */
mhi_chan->state = MHI_CH_STATE_SUSPENDED;
tmp &= ~CHAN_CTX_CHSTATE_MASK;
tmp |= FIELD_PREP(CHAN_CTX_CHSTATE_MASK, MHI_CH_STATE_SUSPENDED);
mhi_cntrl->ch_ctx_cache[i].chcfg = cpu_to_le32(tmp);
mutex_unlock(&mhi_chan->lock);
}
}
for (i = 0; i < mhi_cntrl->max_chan; i++) {
mhi_chan = &mhi_cntrl->mhi_chan[i];
if (!mhi_chan->mhi_dev) continue;
mutex_lock(&mhi_chan->lock); /* Skip if the channel is not currently suspended */
tmp = le32_to_cpu(mhi_cntrl->ch_ctx_cache[i].chcfg); if (FIELD_GET(CHAN_CTX_CHSTATE_MASK, tmp) != MHI_CH_STATE_SUSPENDED) {
mutex_unlock(&mhi_chan->lock); continue;
}
dev_dbg(&mhi_chan->mhi_dev->dev, "Resuming channel\n"); /* Set channel state to RUNNING */
mhi_chan->state = MHI_CH_STATE_RUNNING;
tmp &= ~CHAN_CTX_CHSTATE_MASK;
tmp |= FIELD_PREP(CHAN_CTX_CHSTATE_MASK, MHI_CH_STATE_RUNNING);
mhi_cntrl->ch_ctx_cache[i].chcfg = cpu_to_le32(tmp);
mutex_unlock(&mhi_chan->lock);
}
}
mhi_dev = kzalloc(sizeof(*mhi_dev), GFP_KERNEL); if (!mhi_dev) return ERR_PTR(-ENOMEM);
dev = &mhi_dev->dev;
device_initialize(dev);
dev->bus = &mhi_ep_bus_type;
dev->release = mhi_ep_release_device;
/* Controller device is always allocated first */ if (dev_type == MHI_DEVICE_CONTROLLER) /* for MHI controller device, parent is the bus device (e.g. PCI EPF) */
dev->parent = mhi_cntrl->cntrl_dev; else /* for MHI client devices, parent is the MHI controller device */
dev->parent = &mhi_cntrl->mhi_dev->dev;
/* Check if the channel name is same for both UL and DL */ if (strcmp(mhi_chan->name, mhi_chan[1].name)) {
dev_err(dev, "UL and DL channel names are not same: (%s) != (%s)\n",
mhi_chan->name, mhi_chan[1].name); return -EINVAL;
}
mhi_dev = mhi_ep_alloc_device(mhi_cntrl, MHI_DEVICE_XFER); if (IS_ERR(mhi_dev)) return PTR_ERR(mhi_dev);
/* Configure secondary channel as well */
mhi_chan++;
mhi_dev->dl_chan = mhi_chan;
get_device(&mhi_dev->dev);
mhi_chan->mhi_dev = mhi_dev;
/* Channel name is same for both UL and DL */
mhi_dev->name = mhi_chan->name;
ret = dev_set_name(&mhi_dev->dev, "%s_%s",
dev_name(&mhi_cntrl->mhi_dev->dev),
mhi_dev->name); if (ret) {
put_device(&mhi_dev->dev); return ret;
}
ret = device_add(&mhi_dev->dev); if (ret)
put_device(&mhi_dev->dev);
for (i = 0; i < config->num_channels; i++) { struct mhi_ep_chan *mhi_chan;
ch_cfg = &config->ch_cfg[i];
chan = ch_cfg->num; if (chan >= mhi_cntrl->max_chan) {
dev_err(dev, "Channel (%u) exceeds maximum available channels (%u)\n",
chan, mhi_cntrl->max_chan); goto error_chan_cfg;
}
/* Bi-directional and direction less channels are not supported */ if (ch_cfg->dir == DMA_BIDIRECTIONAL || ch_cfg->dir == DMA_NONE) {
dev_err(dev, "Invalid direction (%u) for channel (%u)\n",
ch_cfg->dir, chan); goto error_chan_cfg;
}
/* Set MHI version and AMSS EE before enumeration */
mhi_ep_mmio_write(mhi_cntrl, EP_MHIVER, config->mhi_version);
mhi_ep_mmio_set_env(mhi_cntrl, MHI_EE_AMSS);
/* Set controller index */
ret = ida_alloc(&mhi_ep_cntrl_ida, GFP_KERNEL); if (ret < 0) goto err_destroy_wq;
mhi_cntrl->index = ret;
irq_set_status_flags(mhi_cntrl->irq, IRQ_NOAUTOEN);
ret = request_irq(mhi_cntrl->irq, mhi_ep_irq, IRQF_TRIGGER_HIGH, "doorbell_irq", mhi_cntrl); if (ret) {
dev_err(mhi_cntrl->cntrl_dev, "Failed to request Doorbell IRQ\n"); goto err_ida_free;
}
/* Allocate the controller device */
mhi_dev = mhi_ep_alloc_device(mhi_cntrl, MHI_DEVICE_CONTROLLER); if (IS_ERR(mhi_dev)) {
dev_err(mhi_cntrl->cntrl_dev, "Failed to allocate controller device\n");
ret = PTR_ERR(mhi_dev); goto err_free_irq;
}
ret = dev_set_name(&mhi_dev->dev, "mhi_ep%u", mhi_cntrl->index); if (ret) goto err_put_dev;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.