/* * hv_gpadl_size - Return the real size of a gpadl, the size that Hyper-V uses * * For BUFFER gpadl, Hyper-V uses the exact same size as the guest does. * * For RING gpadl, in each ring, the guest uses one PAGE_SIZE as the header * (because of the alignment requirement), however, the hypervisor only * uses the first HV_HYP_PAGE_SIZE as the header, therefore leaving a * (PAGE_SIZE - HV_HYP_PAGE_SIZE) gap. And since there are two rings in a * ringbuffer, the total size for a RING gpadl that Hyper-V uses is the * total size that the guest uses minus twice of the gap size.
*/ staticinline u32 hv_gpadl_size(enum hv_gpadl_type type, u32 size)
{ switch (type) { case HV_GPADL_BUFFER: return size; case HV_GPADL_RING: /* The size of a ringbuffer must be page-aligned */
BUG_ON(size % PAGE_SIZE); /* * Two things to notice here: * 1) We're processing two ring buffers as a unit * 2) We're skipping any space larger than HV_HYP_PAGE_SIZE in * the first guest-size page of each of the two ring buffers. * So we effectively subtract out two guest-size pages, and add * back two Hyper-V size pages.
*/ return size - 2 * (PAGE_SIZE - HV_HYP_PAGE_SIZE);
}
BUG(); return 0;
}
/* * hv_ring_gpadl_send_hvpgoffset - Calculate the send offset (in unit of * HV_HYP_PAGE) in a ring gpadl based on the * offset in the guest * * @offset: the offset (in bytes) where the send ringbuffer starts in the * virtual address space of the guest
*/ staticinline u32 hv_ring_gpadl_send_hvpgoffset(u32 offset)
{
/* * For RING gpadl, in each ring, the guest uses one PAGE_SIZE as the * header (because of the alignment requirement), however, the * hypervisor only uses the first HV_HYP_PAGE_SIZE as the header, * therefore leaving a (PAGE_SIZE - HV_HYP_PAGE_SIZE) gap. * * And to calculate the effective send offset in gpadl, we need to * substract this gap.
*/ return (offset - (PAGE_SIZE - HV_HYP_PAGE_SIZE)) >> HV_HYP_PAGE_SHIFT;
}
/* * hv_gpadl_hvpfn - Return the Hyper-V page PFN of the @i th Hyper-V page in * the gpadl * * @type: the type of the gpadl * @kbuffer: the pointer to the gpadl in the guest * @size: the total size (in bytes) of the gpadl * @send_offset: the offset (in bytes) where the send ringbuffer starts in the * virtual address space of the guest * @i: the index
*/ staticinline u64 hv_gpadl_hvpfn(enum hv_gpadl_type type, void *kbuffer,
u32 size, u32 send_offset, int i)
{ int send_idx = hv_ring_gpadl_send_hvpgoffset(send_offset); unsignedlong delta = 0UL;
switch (type) { case HV_GPADL_BUFFER: break; case HV_GPADL_RING: if (i == 0)
delta = 0; elseif (i <= send_idx)
delta = PAGE_SIZE - HV_HYP_PAGE_SIZE; else
delta = 2 * (PAGE_SIZE - HV_HYP_PAGE_SIZE); break; default:
BUG(); break;
}
/* * vmbus_setevent- Trigger an event notification on the specified * channel.
*/ void vmbus_setevent(struct vmbus_channel *channel)
{ struct hv_monitor_page *monitorpage;
trace_vmbus_setevent(channel);
/* * For channels marked as in "low latency" mode * bypass the monitor page mechanism.
*/ if (channel->offermsg.monitor_allocated && !channel->low_latency) {
vmbus_send_interrupt(channel->offermsg.child_relid);
/* Get the child to parent monitor page */
monitorpage = vmbus_connection.monitor_pages[1];
/* vmbus_free_ring - drop mapping of ring buffer */ void vmbus_free_ring(struct vmbus_channel *channel)
{
hv_ringbuffer_cleanup(&channel->outbound);
hv_ringbuffer_cleanup(&channel->inbound);
if (channel->ringbuffer_page) { /* In a CoCo VM leak the memory if it didn't get re-encrypted */ if (!channel->ringbuffer_gpadlhandle.decrypted)
__free_pages(channel->ringbuffer_page,
get_order(channel->ringbuffer_pagecount
<< PAGE_SHIFT));
channel->ringbuffer_page = NULL;
}
}
EXPORT_SYMBOL_GPL(vmbus_free_ring);
/* vmbus_alloc_ring - allocate and map pages for ring buffer */ int vmbus_alloc_ring(struct vmbus_channel *newchannel,
u32 send_size, u32 recv_size)
{ struct page *page; int order;
if (send_size % PAGE_SIZE || recv_size % PAGE_SIZE) return -EINVAL;
/* Allocate the ring buffer */
order = get_order(send_size + recv_size);
page = alloc_pages_node(cpu_to_node(newchannel->target_cpu),
GFP_KERNEL|__GFP_ZERO, order);
if (!page)
page = alloc_pages(GFP_KERNEL|__GFP_ZERO, order);
/* Used for Hyper-V Socket: a guest client's connect() to the host */ int vmbus_send_tl_connect_request(const guid_t *shv_guest_servie_id, const guid_t *shv_host_servie_id)
{ struct vmbus_channel_tl_connect_request conn_msg; int ret;
ret = vmbus_post_msg(msg, sizeof(*msg), true);
trace_vmbus_send_modifychannel(msg, ret); if (ret != 0) {
spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);
list_del(&info->msglistentry);
spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags); goto free_info;
}
/* * Release channel_mutex; otherwise, vmbus_onoffer_rescind() could block on * the mutex and be unable to signal the completion. * * See the caller target_cpu_store() for information about the usage of the * mutex.
*/
mutex_unlock(&vmbus_connection.channel_mutex);
wait_for_completion(&info->waitevent);
mutex_lock(&vmbus_connection.channel_mutex);
if (info->response.modify_response.status)
ret = -EAGAIN;
free_info:
kfree(info); return ret;
}
/* * Set/change the vCPU (@target_vp) the channel (@child_relid) will interrupt. * * CHANNELMSG_MODIFYCHANNEL messages are aynchronous. When VMbus version 5.3 * or later is negotiated, Hyper-V always sends an ACK in response to such a * message. For VMbus version 5.2 and earlier, it never sends an ACK. With- * out an ACK, we can not know when the host will stop interrupting the "old" * vCPU and start interrupting the "new" vCPU for the given channel. * * The CHANNELMSG_MODIFYCHANNEL message type is supported since VMBus version * VERSION_WIN10_V4_1.
*/ int vmbus_send_modifychannel(struct vmbus_channel *channel, u32 target_vp)
{ if (vmbus_proto_version >= VERSION_WIN10_V5_3) return send_modifychannel_with_ack(channel, target_vp); return send_modifychannel_without_ack(channel, target_vp);
}
EXPORT_SYMBOL_GPL(vmbus_send_modifychannel);
/* * create_gpadl_header - Creates a gpadl for the specified buffer
*/ staticint create_gpadl_header(enum hv_gpadl_type type, void *kbuffer,
u32 size, u32 send_offset, struct vmbus_channel_msginfo **msginfo)
{ int i; int pagecount; struct vmbus_channel_gpadl_header *gpadl_header; struct vmbus_channel_gpadl_body *gpadl_body; struct vmbus_channel_msginfo *msgheader; struct vmbus_channel_msginfo *msgbody = NULL;
u32 msgsize;
/* how many pfns can we fit in a body message */
pfnsize = MAX_SIZE_CHANNEL_MESSAGE - sizeof(struct vmbus_channel_gpadl_body);
pfncount = pfnsize / sizeof(u64);
/* * If pfnleft is zero, everything fits in the header and no body * messages are needed
*/ while (pfnleft) {
pfncurr = umin(pfncount, pfnleft);
msgsize = sizeof(struct vmbus_channel_msginfo) + sizeof(struct vmbus_channel_gpadl_body) +
pfncurr * sizeof(u64);
msgbody = kzalloc(msgsize, GFP_KERNEL);
if (!msgbody) { struct vmbus_channel_msginfo *pos = NULL; struct vmbus_channel_msginfo *tmp = NULL; /* * Free up all the allocated messages.
*/
list_for_each_entry_safe(pos, tmp,
&msgheader->submsglist,
msglistentry) {
/* * Gpadl is u32 and we are using a pointer which could * be 64-bit * This is governed by the guest/host protocol and * so the hypervisor guarantees that this is ok.
*/ for (i = 0; i < pfncurr; i++)
gpadl_body->pfn[i] = hv_gpadl_hvpfn(type,
kbuffer, size, send_offset, pfnsum + i);
/* * __vmbus_establish_gpadl - Establish a GPADL for a buffer or ringbuffer * * @channel: a channel * @type: the type of the corresponding GPADL, only meaningful for the guest. * @kbuffer: from kmalloc or vmalloc * @size: page-size multiple * @send_offset: the offset (in bytes) where the send ring buffer starts, * should be 0 for BUFFER type gpadl * @gpadl_handle: some funky thing
*/ staticint __vmbus_establish_gpadl(struct vmbus_channel *channel, enum hv_gpadl_type type, void *kbuffer,
u32 size, u32 send_offset, struct vmbus_gpadl *gpadl)
{ struct vmbus_channel_gpadl_header *gpadlmsg; struct vmbus_channel_gpadl_body *gpadl_body; struct vmbus_channel_msginfo *msginfo = NULL; struct vmbus_channel_msginfo *submsginfo, *tmp; struct list_head *curr;
u32 next_gpadl_handle; unsignedlong flags; int ret = 0;
ret = create_gpadl_header(type, kbuffer, size, send_offset, &msginfo); if (ret) {
gpadl->decrypted = false; return ret;
}
/* * Set the "decrypted" flag to true for the set_memory_decrypted() * success case. In the failure case, the encryption state of the * memory is unknown. Leave "decrypted" as true to ensure the * memory will be leaked instead of going back on the free list.
*/
gpadl->decrypted = true;
ret = set_memory_decrypted((unsignedlong)kbuffer,
PFN_UP(size)); if (ret) {
dev_warn(&channel->device_obj->device, "Failed to set host visibility for new GPADL %d.\n",
ret); return ret;
}
if (ret) { /* * If set_memory_encrypted() fails, the decrypted flag is * left as true so the memory is leaked instead of being * put back on the free list.
*/ if (!set_memory_encrypted((unsignedlong)kbuffer, PFN_UP(size)))
gpadl->decrypted = false;
}
return ret;
}
/* * vmbus_establish_gpadl - Establish a GPADL for the specified buffer * * @channel: a channel * @kbuffer: from kmalloc or vmalloc * @size: page-size multiple * @gpadl_handle: some funky thing
*/ int vmbus_establish_gpadl(struct vmbus_channel *channel, void *kbuffer,
u32 size, struct vmbus_gpadl *gpadl)
{ return __vmbus_establish_gpadl(channel, HV_GPADL_BUFFER, kbuffer, size,
0U, gpadl);
}
EXPORT_SYMBOL_GPL(vmbus_establish_gpadl);
/** * request_arr_init - Allocates memory for the requestor array. Each slot * keeps track of the next available slot in the array. Initially, each * slot points to the next one (as in a Linked List). The last slot * does not point to anything, so its value is U64_MAX by default. * @size The size of the array
*/ static u64 *request_arr_init(u32 size)
{ int i;
u64 *req_arr;
req_arr = kcalloc(size, sizeof(u64), GFP_KERNEL); if (!req_arr) return NULL;
for (i = 0; i < size - 1; i++)
req_arr[i] = i + 1;
/* Last slot (no more available slots) */
req_arr[i] = U64_MAX;
return req_arr;
}
/* * vmbus_alloc_requestor - Initializes @rqstor's fields. * Index 0 is the first free slot * @size: Size of the requestor array
*/ staticint vmbus_alloc_requestor(struct vmbus_requestor *rqstor, u32 size)
{
u64 *rqst_arr; unsignedlong *bitmap;
rqst_arr = request_arr_init(size); if (!rqst_arr) return -ENOMEM;
if (newchannel->state != CHANNEL_OPEN_STATE) return -EINVAL;
/* Create and init requestor */ if (newchannel->rqstor_size) { if (vmbus_alloc_requestor(&newchannel->requestor, newchannel->rqstor_size)) return -ENOMEM;
}
open_msg = (struct vmbus_channel_open_channel *)open_info->msg;
open_msg->header.msgtype = CHANNELMSG_OPENCHANNEL;
open_msg->openid = newchannel->offermsg.child_relid;
open_msg->child_relid = newchannel->offermsg.child_relid;
open_msg->ringbuffer_gpadlhandle
= newchannel->ringbuffer_gpadlhandle.gpadl_handle; /* * The unit of ->downstream_ringbuffer_pageoffset is HV_HYP_PAGE and * the unit of ->ringbuffer_send_offset (i.e. send_pages) is PAGE, so * here we calculate it into HV_HYP_PAGE.
*/
open_msg->downstream_ringbuffer_pageoffset =
hv_ring_gpadl_send_hvpgoffset(send_pages << PAGE_SHIFT);
open_msg->target_vp = hv_cpu_number_to_vp_number(newchannel->target_cpu);
if (userdatalen)
memcpy(open_msg->userdata, userdata, userdatalen);
ret = vmbus_post_msg(msg, sizeof(struct vmbus_channel_gpadl_teardown), true);
trace_vmbus_teardown_gpadl(msg, ret);
if (ret) goto post_msg_err;
wait_for_completion(&info->waitevent);
gpadl->gpadl_handle = 0;
post_msg_err: /* * If the channel has been rescinded; * we will be awakened by the rescind * handler; set the error code to zero so we don't leak memory.
*/ if (channel->rescind)
ret = 0;
ret = set_memory_encrypted((unsignedlong)gpadl->buffer,
PFN_UP(gpadl->size)); if (ret)
pr_warn("Fail to set mem host visibility in GPADL teardown %d.\n", ret);
/* * vmbus_on_event(), running in the per-channel tasklet, can race * with vmbus_close_internal() in the case of SMP guest, e.g., when * the former is accessing channel->inbound.ring_buffer, the latter * could be freeing the ring_buffer pages, so here we must stop it * first. * * vmbus_chan_sched() might call the netvsc driver callback function * that ends up scheduling NAPI work that accesses the ring buffer. * At this point, we have to ensure that any such work is completed * and that the channel ring buffer is no longer being accessed, cf. * the calls to napi_disable() in netvsc_device_remove().
*/
tasklet_disable(&channel->callback_event);
/* See the inline comments in vmbus_chan_sched(). */
spin_lock_irqsave(&channel->sched_lock, flags);
channel->onchannel_callback = NULL;
spin_unlock_irqrestore(&channel->sched_lock, flags);
channel->sc_creation_callback = NULL;
/* Re-enable tasklet for use on re-open */
tasklet_enable(&channel->callback_event);
}
staticint vmbus_close_internal(struct vmbus_channel *channel)
{ struct vmbus_channel_close_channel *msg; int ret;
vmbus_reset_channel_cb(channel);
/* * In case a device driver's probe() fails (e.g., * util_probe() -> vmbus_open() returns -ENOMEM) and the device is * rescinded later (e.g., we dynamically disable an Integrated Service * in Hyper-V Manager), the driver's remove() invokes vmbus_close(): * here we should skip most of the below cleanup work.
*/ if (channel->state != CHANNEL_OPENED_STATE) return -EINVAL;
ret = vmbus_post_msg(msg, sizeof(struct vmbus_channel_close_channel), true);
trace_vmbus_close_internal(msg, ret);
if (ret) {
pr_err("Close failed: close post msg return is %d\n", ret); /* * If we failed to post the close msg, * it is perhaps better to leak memory.
*/
}
/* Tear down the gpadl for the channel's ring buffer */ elseif (channel->ringbuffer_gpadlhandle.gpadl_handle) {
ret = vmbus_teardown_gpadl(channel, &channel->ringbuffer_gpadlhandle); if (ret) {
pr_err("Close failed: teardown gpadl return %d\n", ret); /* * If we failed to teardown gpadl, * it is perhaps better to leak memory.
*/
}
}
if (!ret)
vmbus_free_requestor(&channel->requestor);
return ret;
}
/* disconnect ring - close all channels */ int vmbus_disconnect_ring(struct vmbus_channel *channel)
{ struct vmbus_channel *cur_channel, *tmp; int ret;
if (channel->primary_channel != NULL) return -EINVAL;
list_for_each_entry_safe(cur_channel, tmp, &channel->sc_list, sc_list) { if (cur_channel->rescind)
wait_for_completion(&cur_channel->rescind_event);
mutex_lock(&vmbus_connection.channel_mutex); if (vmbus_close_internal(cur_channel) == 0) {
vmbus_free_ring(cur_channel);
if (cur_channel->rescind)
hv_process_channel_removal(cur_channel);
}
mutex_unlock(&vmbus_connection.channel_mutex);
}
/* * Now close the primary.
*/
mutex_lock(&vmbus_connection.channel_mutex);
ret = vmbus_close_internal(channel);
mutex_unlock(&vmbus_connection.channel_mutex);
/* * vmbus_close - Close the specified channel
*/ void vmbus_close(struct vmbus_channel *channel)
{ if (vmbus_disconnect_ring(channel) == 0)
vmbus_free_ring(channel);
}
EXPORT_SYMBOL_GPL(vmbus_close);
/** * vmbus_sendpacket_getid() - Send the specified buffer on the given channel * @channel: Pointer to vmbus_channel structure * @buffer: Pointer to the buffer you want to send the data from. * @bufferlen: Maximum size of what the buffer holds. * @requestid: Identifier of the request * @trans_id: Identifier of the transaction associated to this request, if * the send is successful; undefined, otherwise. * @type: Type of packet that is being sent e.g. negotiate, time * packet etc. * @flags: 0 or VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED * * Sends data in @buffer directly to Hyper-V via the vmbus. * This will send the data unparsed to Hyper-V. * * Mainly used by Hyper-V drivers.
*/ int vmbus_sendpacket_getid(struct vmbus_channel *channel, void *buffer,
u32 bufferlen, u64 requestid, u64 *trans_id, enum vmbus_packet_type type, u32 flags)
{ struct vmpacket_descriptor desc;
u32 packetlen = sizeof(struct vmpacket_descriptor) + bufferlen;
u32 packetlen_aligned = ALIGN(packetlen, sizeof(u64)); struct kvec bufferlist[3];
u64 aligned_data = 0; int num_vecs = ((bufferlen != 0) ? 3 : 1);
/* Setup the descriptor */
desc.type = type; /* VmbusPacketTypeDataInBand; */
desc.flags = flags; /* VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED; */ /* in 8-bytes granularity */
desc.offset8 = sizeof(struct vmpacket_descriptor) >> 3;
desc.len8 = (u16)(packetlen_aligned >> 3);
desc.trans_id = VMBUS_RQST_ERROR; /* will be updated in hv_ringbuffer_write() */
/** * vmbus_sendpacket() - Send the specified buffer on the given channel * @channel: Pointer to vmbus_channel structure * @buffer: Pointer to the buffer you want to send the data from. * @bufferlen: Maximum size of what the buffer holds. * @requestid: Identifier of the request * @type: Type of packet that is being sent e.g. negotiate, time * packet etc. * @flags: 0 or VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED * * Sends data in @buffer directly to Hyper-V via the vmbus. * This will send the data unparsed to Hyper-V. * * Mainly used by Hyper-V drivers.
*/ int vmbus_sendpacket(struct vmbus_channel *channel, void *buffer,
u32 bufferlen, u64 requestid, enum vmbus_packet_type type, u32 flags)
{ return vmbus_sendpacket_getid(channel, buffer, bufferlen,
requestid, NULL, type, flags);
}
EXPORT_SYMBOL(vmbus_sendpacket);
/* * vmbus_sendpacket_mpb_desc - Send one or more multi-page buffer packets * using a GPADL Direct packet type. * The desc argument must include space for the VMBus descriptor. The * rangecount field must already be set.
*/ int vmbus_sendpacket_mpb_desc(struct vmbus_channel *channel, struct vmbus_packet_mpb_array *desc,
u32 desc_size, void *buffer, u32 bufferlen, u64 requestid)
{
u32 packetlen;
u32 packetlen_aligned; struct kvec bufferlist[3];
u64 aligned_data = 0;
/** * __vmbus_recvpacket() - Retrieve the user packet on the specified channel * @channel: Pointer to vmbus_channel structure * @buffer: Pointer to the buffer you want to receive the data into. * @bufferlen: Maximum size of what the buffer can hold. * @buffer_actual_len: The actual size of the data after it was received. * @requestid: Identifier of the request * @raw: true means keep the vmpacket_descriptor header in the received data. * * Receives directly from the hyper-v vmbus and puts the data it received * into Buffer. This will receive the data unparsed from hyper-v. * * Mainly used by Hyper-V drivers.
*/ staticinlineint
__vmbus_recvpacket(struct vmbus_channel *channel, void *buffer,
u32 bufferlen, u32 *buffer_actual_len, u64 *requestid, bool raw)
{ return hv_ringbuffer_read(channel, buffer, bufferlen,
buffer_actual_len, requestid, raw);
/* * vmbus_recvpacket_raw - Retrieve the raw packet on the specified channel
*/ int vmbus_recvpacket_raw(struct vmbus_channel *channel, void *buffer,
u32 bufferlen, u32 *buffer_actual_len,
u64 *requestid)
{ return __vmbus_recvpacket(channel, buffer, bufferlen,
buffer_actual_len, requestid, true);
}
EXPORT_SYMBOL_GPL(vmbus_recvpacket_raw);
/* * vmbus_next_request_id - Returns a new request id. It is also * the index at which the guest memory address is stored. * Uses a spin lock to avoid race conditions. * @channel: Pointer to the VMbus channel struct * @rqst_add: Guest memory address to be stored in the array
*/
u64 vmbus_next_request_id(struct vmbus_channel *channel, u64 rqst_addr)
{ struct vmbus_requestor *rqstor = &channel->requestor; unsignedlong flags;
u64 current_id;
/* Check rqstor has been initialized */ if (!channel->rqstor_size) return VMBUS_NO_RQSTOR;
/* The already held spin lock provides atomicity */
bitmap_set(rqstor->req_bitmap, current_id, 1);
unlock_requestor(channel, flags);
/* * Cannot return an ID of 0, which is reserved for an unsolicited * message from Hyper-V; Hyper-V does not acknowledge (respond to) * VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED requests with ID of * 0 sent by the guest.
*/ return current_id + 1;
}
EXPORT_SYMBOL_GPL(vmbus_next_request_id);
/* As in vmbus_request_addr_match() but without the requestor lock */
u64 __vmbus_request_addr_match(struct vmbus_channel *channel, u64 trans_id,
u64 rqst_addr)
{ struct vmbus_requestor *rqstor = &channel->requestor;
u64 req_addr;
/* Check rqstor has been initialized */ if (!channel->rqstor_size) return VMBUS_NO_RQSTOR;
/* Hyper-V can send an unsolicited message with ID of 0 */ if (!trans_id) return VMBUS_RQST_ERROR;
/* Data corresponding to trans_id is stored at trans_id - 1 */
trans_id--;
/* * vmbus_request_addr_match - Clears/removes @trans_id from the @channel's * requestor, provided the memory address stored at @trans_id equals @rqst_addr * (or provided @rqst_addr matches the sentinel value VMBUS_RQST_ADDR_ANY). * * Returns the memory address stored at @trans_id, or VMBUS_RQST_ERROR if * @trans_id is not contained in the requestor. * * Acquires and releases the requestor spin lock.
*/
u64 vmbus_request_addr_match(struct vmbus_channel *channel, u64 trans_id,
u64 rqst_addr)
{ unsignedlong flags;
u64 req_addr;
/* * vmbus_request_addr - Returns the memory address stored at @trans_id * in @rqstor. Uses a spin lock to avoid race conditions. * @channel: Pointer to the VMbus channel struct * @trans_id: Request id sent back from Hyper-V. Becomes the requestor's * next request id.
*/
u64 vmbus_request_addr(struct vmbus_channel *channel, u64 trans_id)
{ return vmbus_request_addr_match(channel, trans_id, VMBUS_RQST_ADDR_ANY);
}
EXPORT_SYMBOL_GPL(vmbus_request_addr);
Messung V0.5
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet)
¤
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.