/* * If the port is down, clean up all pending traps. We need to be careful * with the given trap, because it may be queued.
*/ staticvoid cleanup_traps(struct hfi1_ibport *ibp, struct trap_node *trap)
{ struct trap_node *node, *q; unsignedlong flags; struct list_head trap_list; int i;
for (i = 0; i < RVT_MAX_TRAP_LISTS; i++) {
spin_lock_irqsave(&ibp->rvp.lock, flags);
list_replace_init(&ibp->rvp.trap_lists[i].list, &trap_list);
ibp->rvp.trap_lists[i].list_len = 0;
spin_unlock_irqrestore(&ibp->rvp.lock, flags);
/* * Remove all items from the list, freeing all the non-given * traps.
*/
list_for_each_entry_safe(node, q, &trap_list, list) {
list_del(&node->list); if (node != trap)
kfree(node);
}
}
/* * If this wasn't on one of the lists it would not be freed. If it * was on the list, it is now safe to free.
*/
kfree(trap);
}
/* * Since the retry (handle timeout) does not remove a trap request * from the list, all we have to do is compare the node.
*/
spin_lock_irqsave(&ibp->rvp.lock, flags);
trap_list = &ibp->rvp.trap_lists[queue_id];
list_for_each_entry(node, &trap_list->list, list) { if (node == trap) {
node->retry++;
found = 1; break;
}
}
/* If it is not on the list, add it, limited to RVT-MAX_TRAP_LEN. */ if (!found) { if (trap_list->list_len < RVT_MAX_TRAP_LEN) {
trap_list->list_len++;
list_add_tail(&trap->list, &trap_list->list);
} else {
pr_warn_ratelimited("hfi1: Maximum trap limit reached for 0x%0x traps\n",
trap->data.generic_type);
kfree(trap);
}
}
/* * Next check to see if there is a timer pending. If not, set it up * and get the first trap from the list.
*/
node = NULL; if (!timer_pending(&ibp->rvp.trap_timer)) { /* * o14-2 * If the time out is set we have to wait until it expires * before the trap can be sent. * This should be > RVT_TRAP_TIMEOUT
*/
timeout = (RVT_TRAP_TIMEOUT *
(1UL << ibp->rvp.subnet_timeout)) / 1000;
mod_timer(&ibp->rvp.trap_timer,
jiffies + usecs_to_jiffies(timeout));
node = list_first_entry(&trap_list->list, struct trap_node,
list);
node->in_use = 1;
}
spin_unlock_irqrestore(&ibp->rvp.lock, flags);
/* Only update the transaction ID for new traps (o13-5). */ if (trap->tid == 0) {
ibp->rvp.tid++; /* make sure that tid != 0 */ if (ibp->rvp.tid == 0)
ibp->rvp.tid++;
trap->tid = cpu_to_be64(ibp->rvp.tid);
}
smp->tid = trap->tid;
/* * If the trap was repressed while things were getting set up, don't * bother sending it. This could happen for a retry.
*/ if (trap->repress) {
list_del(&trap->list);
spin_unlock_irqrestore(&ibp->rvp.lock, flags);
kfree(trap);
ib_free_send_mad(send_buf); return;
}
staticint check_mkey(struct hfi1_ibport *ibp, struct ib_mad_hdr *mad, int mad_flags, __be64 mkey, __be32 dr_slid,
u8 return_path[], u8 hop_cnt)
{ int valid_mkey = 0; int ret = 0;
/* Is the mkey in the process of expiring? */ if (ibp->rvp.mkey_lease_timeout &&
time_after_eq(jiffies, ibp->rvp.mkey_lease_timeout)) { /* Clear timeout and mkey protection field. */
ibp->rvp.mkey_lease_timeout = 0;
ibp->rvp.mkeyprot = 0;
}
/* Unset lease timeout on any valid Get/Set/TrapRepress */ if (valid_mkey && ibp->rvp.mkey_lease_timeout &&
(mad->method == IB_MGMT_METHOD_GET ||
mad->method == IB_MGMT_METHOD_SET ||
mad->method == IB_MGMT_METHOD_TRAP_REPRESS))
ibp->rvp.mkey_lease_timeout = 0;
if (!valid_mkey) { switch (mad->method) { case IB_MGMT_METHOD_GET: /* Bad mkey not a violation below level 2 */ if (ibp->rvp.mkeyprot < 2) break;
fallthrough; case IB_MGMT_METHOD_SET: case IB_MGMT_METHOD_TRAP_REPRESS: if (ibp->rvp.mkey_violations != 0xFFFF)
++ibp->rvp.mkey_violations; if (!ibp->rvp.mkey_lease_timeout &&
ibp->rvp.mkey_lease_period)
ibp->rvp.mkey_lease_timeout = jiffies +
ibp->rvp.mkey_lease_period * HZ; /* Generate a trap notice. */
bad_mkey(ibp, mad, mkey, dr_slid, return_path,
hop_cnt);
ret = 1;
}
}
return ret;
}
/* * The SMA caches reads from LCB registers in case the LCB is unavailable. * (The LCB is unavailable in certain link states, for example.)
*/ struct lcb_datum {
u32 off;
u64 val;
};
/* Only return the mkey if the protection field allows it. */ if (!(smp->method == IB_MGMT_METHOD_GET &&
ibp->rvp.mkey != smp->mkey &&
ibp->rvp.mkeyprot == 1))
pi->mkey = ibp->rvp.mkey;
if (start_of_sm_config && (state == IB_PORT_INIT))
ppd->is_sm_config_started = 1;
pi->port_phys_conf = (ppd->port_type & 0xf);
pi->port_states.ledenable_offlinereason = ppd->neighbor_normal << 4;
pi->port_states.ledenable_offlinereason |=
ppd->is_sm_config_started << 5; /* * This pairs with the memory barrier in hfi1_start_led_override to * ensure that we read the correct state of LED beaconing represented * by led_override_timer_active
*/
smp_rmb();
is_beaconing_active = !!atomic_read(&ppd->led_override_timer_active);
pi->port_states.ledenable_offlinereason |= is_beaconing_active << 6;
pi->port_states.ledenable_offlinereason |=
ppd->offline_disabled_reason;
/* HFI supports a replay buffer 128 LTPs in size */
pi->replay_depth.buffer = 0x80; /* read the cached value of DC_LCB_STS_ROUND_TRIP_LTP_CNT */
read_lcb_cache(DC_LCB_STS_ROUND_TRIP_LTP_CNT, &tmp);
/* * this counter is 16 bits wide, but the replay_depth.wire * variable is only 8 bits
*/ if (tmp > 0xff)
tmp = 0xff;
pi->replay_depth.wire = tmp;
if (resp_len)
*resp_len += sizeof(struct opa_port_info);
return reply((struct ib_mad_hdr *)smp);
}
/** * get_pkeys - return the PKEY table * @dd: the hfi1_ib device * @port: the IB port number * @pkeys: the pkey table is placed here
*/ staticint get_pkeys(struct hfi1_devdata *dd, u32 port, u16 *pkeys)
{ struct hfi1_pportdata *ppd = dd->pport + port - 1;
if (start_block + n_blocks_req > n_blocks_avail ||
n_blocks_req > OPA_NUM_PKEY_BLOCKS_PER_SMP) {
pr_warn("OPA Get PKey AM Invalid : s 0x%x; req 0x%x; " "avail 0x%x; blk/smp 0x%lx\n",
start_block, n_blocks_req, n_blocks_avail,
OPA_NUM_PKEY_BLOCKS_PER_SMP);
smp->status |= IB_SMP_INVALID_FIELD; return reply((struct ib_mad_hdr *)smp);
}
p = (__be16 *)data;
q = (u16 *)data; /* get the real pkeys if we are requesting the first block */ if (start_block == 0) {
get_pkeys(dd, port, q); for (i = 0; i < npkeys; i++)
p[i] = cpu_to_be16(q[i]); if (resp_len)
*resp_len += size;
} else {
smp->status |= IB_SMP_INVALID_FIELD;
} return reply((struct ib_mad_hdr *)smp);
}
ret = logical_transition_allowed(logical_old, logical_new);
logical_allowed = ret;
if (ret == HFI_TRANSITION_DISALLOWED ||
ret == HFI_TRANSITION_UNDEFINED) {
pr_warn("invalid logical state transition %s -> %s\n",
ib_port_state_to_str(logical_old),
ib_port_state_to_str(logical_new)); return ret;
}
ret = physical_transition_allowed(physical_old, physical_new);
physical_allowed = ret;
if (ret == HFI_TRANSITION_DISALLOWED ||
ret == HFI_TRANSITION_UNDEFINED) {
pr_warn("invalid physical state transition %s -> %s\n",
opa_pstate_name(physical_old),
opa_pstate_name(physical_new)); return ret;
}
if (logical_allowed == HFI_TRANSITION_IGNORED &&
physical_allowed == HFI_TRANSITION_IGNORED) return HFI_TRANSITION_IGNORED;
/* * A change request of Physical Port State from * 'Offline' to 'Polling' should be ignored.
*/ if ((physical_old == OPA_PORTPHYSSTATE_OFFLINE) &&
(physical_new == IB_PORTPHYSSTATE_POLLING)) return HFI_TRANSITION_IGNORED;
/* * Either physical_allowed or logical_allowed is * HFI_TRANSITION_ALLOWED.
*/ return HFI_TRANSITION_ALLOWED;
}
ret = port_states_transition_allowed(ppd, logical_state, phys_state); if (ret == HFI_TRANSITION_DISALLOWED ||
ret == HFI_TRANSITION_UNDEFINED) { /* error message emitted above */
smp->status |= IB_SMP_INVALID_FIELD; return 0;
}
if (ret == HFI_TRANSITION_IGNORED) return 0;
if ((phys_state != IB_PORTPHYSSTATE_NOP) &&
!(logical_state == IB_PORT_DOWN ||
logical_state == IB_PORT_NOP)){
pr_warn("SubnSet(OPA_PortInfo) port state invalid: logical_state 0x%x physical_state 0x%x\n",
logical_state, phys_state);
smp->status |= IB_SMP_INVALID_FIELD;
}
/* * Logical state changes are summarized in OPAv1g1 spec., * Table 9-12; physical state changes are summarized in * OPAv1g1 spec., Table 6.4.
*/ switch (logical_state) { case IB_PORT_NOP: if (phys_state == IB_PORTPHYSSTATE_NOP) break;
fallthrough; case IB_PORT_DOWN: if (phys_state == IB_PORTPHYSSTATE_NOP) {
link_state = HLS_DN_DOWNDEF;
} elseif (phys_state == IB_PORTPHYSSTATE_POLLING) {
link_state = HLS_DN_POLL;
set_link_down_reason(ppd, OPA_LINKDOWN_REASON_FM_BOUNCE,
0, OPA_LINKDOWN_REASON_FM_BOUNCE);
} elseif (phys_state == IB_PORTPHYSSTATE_DISABLED) {
link_state = HLS_DN_DISABLE;
} else {
pr_warn("SubnSet(OPA_PortInfo) invalid physical state 0x%x\n",
phys_state);
smp->status |= IB_SMP_INVALID_FIELD; break;
}
if ((link_state == HLS_DN_POLL ||
link_state == HLS_DN_DOWNDEF)) { /* * Going to poll. No matter what the current state, * always move offline first, then tune and start the * link. This correctly handles a FM link bounce and * a link enable. Going offline is a no-op if already * offline.
*/
set_link_state(ppd, HLS_DN_OFFLINE);
start_link(ppd);
} else {
set_link_state(ppd, link_state);
} if (link_state == HLS_DN_DISABLE &&
(ppd->offline_disabled_reason >
HFI1_ODR_MASK(OPA_LINKDOWN_REASON_SMA_DISABLED) ||
ppd->offline_disabled_reason ==
HFI1_ODR_MASK(OPA_LINKDOWN_REASON_NONE)))
ppd->offline_disabled_reason =
HFI1_ODR_MASK(OPA_LINKDOWN_REASON_SMA_DISABLED); /* * Don't send a reply if the response would be sent * through the disabled port.
*/ if (link_state == HLS_DN_DISABLE && !local_mad) return IB_MAD_RESULT_SUCCESS | IB_MAD_RESULT_CONSUMED; break; case IB_PORT_ARMED:
ret = set_link_state(ppd, HLS_UP_ARMED); if (!ret)
send_idle_sma(dd, SMA_IDLE_ARM); break; case IB_PORT_ACTIVE: if (ppd->neighbor_normal) {
ret = set_link_state(ppd, HLS_UP_ACTIVE); if (ret == 0)
send_idle_sma(dd, SMA_IDLE_ACTIVE);
} else {
pr_warn("SubnSet(OPA_PortInfo) Cannot move to Active with NeighborNormal 0\n");
smp->status |= IB_SMP_INVALID_FIELD;
} break; default:
pr_warn("SubnSet(OPA_PortInfo) invalid logical state 0x%x\n",
logical_state);
smp->status |= IB_SMP_INVALID_FIELD;
}
return 0;
}
/* * subn_set_opa_portinfo - set port information * @smp: the incoming SM packet * @ibdev: the infiniband device * @port: the port on the device *
*/ staticint __subn_set_opa_portinfo(struct opa_smp *smp, u32 am, u8 *data, struct ib_device *ibdev, u32 port,
u32 *resp_len, u32 max_len, int local_mad)
{ struct opa_port_info *pi = (struct opa_port_info *)data; struct ib_event event; struct hfi1_devdata *dd; struct hfi1_pportdata *ppd; struct hfi1_ibport *ibp;
u8 clientrereg; unsignedlong flags;
u32 smlid;
u32 lid;
u8 ls_old, ls_new, ps_new;
u8 vls;
u8 msl;
u8 crc_enabled;
u16 lse, lwe, mtu;
u32 num_ports = OPA_AM_NPORT(am);
u32 start_of_sm_config = OPA_AM_START_SM_CFG(am); int ret, i, invalid = 0, call_set_mtu = 0; int call_link_downgrade_policy = 0;
lid = be32_to_cpu(pi->lid); if (lid & 0xFF000000) {
pr_warn("OPA_PortInfo lid out of range: %X\n", lid);
smp->status |= IB_SMP_INVALID_FIELD; goto get_only;
}
smlid = be32_to_cpu(pi->sm_lid); if (smlid & 0xFF000000) {
pr_warn("OPA_PortInfo SM lid out of range: %X\n", smlid);
smp->status |= IB_SMP_INVALID_FIELD; goto get_only;
}
if (ppd->vls_supported / 2 > ARRAY_SIZE(pi->neigh_mtu.pvlx_to_mtu) ||
ppd->vls_supported > ARRAY_SIZE(dd->vld)) {
smp->status |= IB_SMP_INVALID_FIELD; return reply((struct ib_mad_hdr *)smp);
} for (i = 0; i < ppd->vls_supported; i++) { if ((i % 2) == 0)
mtu = enum_to_mtu((pi->neigh_mtu.pvlx_to_mtu[i / 2] >>
4) & 0xF); else
mtu = enum_to_mtu(pi->neigh_mtu.pvlx_to_mtu[i / 2] &
0xF); if (mtu == 0xffff) {
pr_warn("SubnSet(OPA_PortInfo) mtu invalid %d (0x%x)\n",
mtu,
(pi->neigh_mtu.pvlx_to_mtu[0] >> 4) & 0xF);
smp->status |= IB_SMP_INVALID_FIELD;
mtu = hfi1_max_mtu; /* use a valid MTU */
} if (dd->vld[i].mtu != mtu) {
dd_dev_info(dd, "MTU change on vl %d from %d to %d\n",
i, dd->vld[i].mtu, mtu);
dd->vld[i].mtu = mtu;
call_set_mtu++;
}
} /* As per OPAV1 spec: VL15 must support and be configured * for operation with a 2048 or larger MTU.
*/
mtu = enum_to_mtu(pi->neigh_mtu.pvlx_to_mtu[15 / 2] & 0xF); if (mtu < 2048 || mtu == 0xffff)
mtu = 2048; if (dd->vld[15].mtu != mtu) {
dd_dev_info(dd, "MTU change on vl 15 from %d to %d\n",
dd->vld[15].mtu, mtu);
dd->vld[15].mtu = mtu;
call_set_mtu++;
} if (call_set_mtu)
set_mtu(ppd);
if (ls_old == IB_PORT_INIT) { if (start_of_sm_config) { if (ls_new == ls_old || (ls_new == IB_PORT_ARMED))
ppd->is_sm_config_started = 1;
} elseif (ls_new == IB_PORT_ARMED) { if (ppd->is_sm_config_started == 0) {
invalid = 1;
smp->status |= IB_SMP_INVALID_FIELD;
}
}
}
/* Handle CLIENT_REREGISTER event b/c SM asked us for it */ if (clientrereg) {
event.event = IB_EVENT_CLIENT_REREGISTER;
ib_dispatch_event(&event);
}
/* * Do the port state change now that the other link parameters * have been set. * Changing the port physical state only makes sense if the link * is down or is being set to down.
*/
if (!invalid) {
ret = set_port_states(ppd, smp, ls_new, ps_new, local_mad); if (ret) return ret;
}
ret = __subn_get_opa_portinfo(smp, am, data, ibdev, port, resp_len,
max_len);
/* restore re-reg bit per o14-12.2.1 */
pi->clientrereg_subnettimeout |= clientrereg;
/* * Apply the new link downgrade policy. This may result in a link * bounce. Do this after everything else so things are settled. * Possible problem: if setting the port state above fails, then * the policy change is not applied.
*/ if (call_link_downgrade_policy)
apply_link_downgrade_policy(ppd, 0);
/** * set_pkeys - set the PKEY table for ctxt 0 * @dd: the hfi1_ib device * @port: the IB port number * @pkeys: the PKEY table
*/ staticint set_pkeys(struct hfi1_devdata *dd, u32 port, u16 *pkeys)
{ struct hfi1_pportdata *ppd; int i; int changed = 0; int update_includes_mgmt_partition = 0;
/* * IB port one/two always maps to context zero/one, * always a kernel context, no locking needed * If we get here with ppd setup, no need to check * that rcd is valid.
*/
ppd = dd->pport + (port - 1); /* * If the update does not include the management pkey, don't do it.
*/ for (i = 0; i < ARRAY_SIZE(ppd->pkeys); i++) { if (pkeys[i] == LIM_MGMT_P_KEY) {
update_includes_mgmt_partition = 1; break;
}
}
if (!update_includes_mgmt_partition) return 1;
for (i = 0; i < ARRAY_SIZE(ppd->pkeys); i++) {
u16 key = pkeys[i];
u16 okey = ppd->pkeys[i];
if (key == okey) continue; /* * The SM gives us the complete PKey table. We have * to ensure that we put the PKeys in the matching * slots.
*/
ppd->pkeys[i] = key;
changed = 1;
}
if (changed) {
(void)hfi1_set_ib_cfg(ppd, HFI1_IB_CFG_PKEYS, 0);
hfi1_event_pkey_change(dd, port);
}
#define ILLEGAL_VL 12 /* * filter_sc2vlt changes mappings to VL15 to ILLEGAL_VL (except * for SC15, which must map to VL15). If we don't remap things this * way it is possible for VL15 counters to increment when we try to * send on a SC which is mapped to an invalid VL. * When getting the table convert ILLEGAL_VL back to VL15.
*/ staticvoid filter_sc2vlt(void *data, bool set)
{ int i;
u8 *pd = data;
for (i = 0; i < OPA_MAX_SCS; i++) { if (i == 15) continue;
if (set) { if ((pd[i] & 0x1f) == 0xf)
pd[i] = ILLEGAL_VL;
} else { if ((pd[i] & 0x1f) == ILLEGAL_VL)
pd[i] = 0xf;
}
}
}
staticint __subn_set_opa_sc_to_vlt(struct opa_smp *smp, u32 am, u8 *data, struct ib_device *ibdev, u32 port,
u32 *resp_len, u32 max_len)
{
u32 n_blocks = OPA_AM_NBLK(am); int async_update = OPA_AM_ASYNC(am); struct hfi1_devdata *dd = dd_from_ibdev(ibdev); void *vp = (void *)data; struct hfi1_pportdata *ppd; int lstate; /* * set_sc2vlt_tables writes the information contained in *data * to four 64-bit registers SendSC2VLt[0-3]. We need to make * sure *max_len is not greater than the total size of the four * SendSC2VLt[0-3] registers.
*/
size_t size = 4 * sizeof(u64);
/* IB numbers ports from 1, hw from 0 */
ppd = dd->pport + (port - 1);
lstate = driver_lstate(ppd); /* * it's known that async_update is 0 by this point, but include * the explicit check for clarity
*/ if (!async_update &&
(lstate == IB_PORT_ARMED || lstate == IB_PORT_ACTIVE)) {
smp->status |= IB_SMP_INVALID_FIELD; return reply((struct ib_mad_hdr *)smp);
}
/* * check that addr is within spec, and * addr and (addr + len - 1) are on the same "page"
*/ if (addr >= 4096 ||
(__CI_PAGE_NUM(addr) != __CI_PAGE_NUM(addr + len - 1))) {
smp->status |= IB_SMP_INVALID_FIELD; return reply((struct ib_mad_hdr *)smp);
}
/* The address range for the CableInfo SMA query is wider than the * memory available on the QSFP cable. We want to return a valid * response, albeit zeroed out, for address ranges beyond available * memory but that are within the CableInfo query spec
*/ if (ret < 0 && ret != -ERANGE) {
smp->status |= IB_SMP_INVALID_FIELD; return reply((struct ib_mad_hdr *)smp);
}
/* Request contains first three fields, response contains those plus the rest */ struct opa_port_data_counters_msg {
__be64 port_select_mask[4];
__be32 vl_select_mask;
__be32 resolution;
/** * tx_link_width - convert link width bitmask to integer * value representing actual link width. * @link_width: width of active link * @return: return index of the bit set in link_width var * * The function convert and return the index of bit set * that indicate the current link width.
*/
u16 tx_link_width(u16 link_width)
{ int n = LINK_WIDTH_DEFAULT;
u16 tx_width = n;
/** * get_xmit_wait_counters - Convert HFI 's SendWaitCnt/SendWaitVlCnt * counter in unit of TXE cycle times to flit times. * @ppd: info of physical Hfi port * @link_width: width of active link * @link_speed: speed of active link * @vl: represent VL0-VL7, VL15 for PortVLXmitWait counters request * and if vl value is C_VL_COUNT, it represent SendWaitCnt * counter request * @return: return SendWaitCnt/SendWaitVlCnt counter value per vl. * * Convert SendWaitCnt/SendWaitVlCnt counter from TXE cycle times to * flit times. Call this function to samples these counters. This * function will calculate for previous state transition and update * current state at end of function using ppd->prev_link_width and * ppd->port_vl_xmit_wait_last to port_vl_xmit_wait_curr and link_width.
*/
u64 get_xmit_wait_counters(struct hfi1_pportdata *ppd,
u16 link_width, u16 link_speed, int vl)
{
u64 port_vl_xmit_wait_curr;
u64 delta_vl_xmit_wait;
u64 xmit_wait_val;
/* rsp->uncorrectable_errors is 8 bits wide, and it pegs at 0xff */
tmp = read_dev_cntr(dd, C_DC_UNC_ERR, CNTR_INVALID_VL);
rsp->uncorrectable_errors = tmp < 0x100 ? (tmp & 0xff) : 0xff;
vlinfo = &rsp->vls[0];
vfi = 0; /* The vl_select_mask has been checked above, and we know * that it contains only entries which represent valid VLs. * So in the for_each_set_bit() loop below, we don't need * any additional checks for vl.
*/
for_each_set_bit(vl, &vl_select_mask, BITS_PER_LONG) {
memset(vlinfo, 0, sizeof(*vlinfo));
/* * The bit set in the mask needs to be consistent with the * port the request came in on.
*/
port_mask = be64_to_cpu(req->port_select_mask[3]);
port_num = find_first_bit((unsignedlong *)&port_mask, sizeof(port_mask) * 8);
rsp->port_number = port; /* * Note that link_quality_indicator is a 32 bit quantity in * 'datacounters' queries (as opposed to 'portinfo' queries, * where it's a byte).
*/
hfi1_read_link_quality(dd, &lq);
rsp->link_quality_indicator = cpu_to_be32((u32)lq);
pma_get_opa_port_dctrs(ibdev, rsp);
vlinfo = &rsp->vls[0];
vfi = 0; /* The vl_select_mask has been checked above, and we know * that it contains only entries which represent valid VLs. * So in the for_each_set_bit() loop below, we don't need * any additional checks for vl.
*/
for_each_set_bit(vl, &vl_select_mask, BITS_PER_LONG) {
memset(vlinfo, 0, sizeof(*vlinfo));
if (response_data_size > sizeof(pmp->data)) {
pmp->mad_hdr.status |= IB_SMP_INVALID_FIELD; return reply((struct ib_mad_hdr *)pmp);
} /* * The bit set in the mask needs to be consistent with the * port the request came in on.
*/
port_mask = be64_to_cpu(req->port_select_mask[3]);
port_num = find_first_bit((unsignedlong *)&port_mask, sizeof(port_mask) * 8);
/* * The bit set in the mask needs to be consistent with the port * the request came in on.
*/
port_mask = be64_to_cpu(req->port_select_mask[3]);
port_num = find_first_bit((unsignedlong *)&port_mask, sizeof(port_mask) * 8);
/* ExcessiverBufferOverrunInfo */
reg = read_csr(dd, RCV_ERR_INFO); if (reg & RCV_ERR_INFO_RCV_EXCESS_BUFFER_OVERRUN_SMASK) { /* * if the RcvExcessBufferOverrun bit is set, save SC of * first pkt that encountered an excess buffer overrun
*/
u8 tmp = (u8)reg;
tmp &= RCV_ERR_INFO_RCV_EXCESS_BUFFER_OVERRUN_SC_SMASK;
tmp <<= 2;
rsp->excessive_buffer_overrun_ei.status_and_sc = tmp; /* set the status bit */
rsp->excessive_buffer_overrun_ei.status_and_sc |= 0x80;
}
if ((nports != 1) || (portn != 1 << port)) {
pmp->mad_hdr.status |= IB_SMP_INVALID_FIELD; return reply((struct ib_mad_hdr *)pmp);
} /* * only counters returned by pma_get_opa_portstatus() are * handled, so when pma_get_opa_portstatus() gets a fix, * the corresponding change should be made here as well.
*/
if (counter_select & CS_PORT_XMIT_DATA)
write_dev_cntr(dd, C_DC_XMIT_FLITS, CNTR_INVALID_VL, 0);
if (counter_select & CS_PORT_RCV_DATA)
write_dev_cntr(dd, C_DC_RCV_FLITS, CNTR_INVALID_VL, 0);
if (counter_select & CS_PORT_XMIT_PKTS)
write_dev_cntr(dd, C_DC_XMIT_PKTS, CNTR_INVALID_VL, 0);
if (counter_select & CS_PORT_RCV_PKTS)
write_dev_cntr(dd, C_DC_RCV_PKTS, CNTR_INVALID_VL, 0);
if (counter_select & CS_PORT_MCAST_XMIT_PKTS)
write_dev_cntr(dd, C_DC_MC_XMIT_PKTS, CNTR_INVALID_VL, 0);
if (counter_select & CS_PORT_MCAST_RCV_PKTS)
write_dev_cntr(dd, C_DC_MC_RCV_PKTS, CNTR_INVALID_VL, 0);
if (counter_select & CS_PORT_RCV_FECN)
write_dev_cntr(dd, C_DC_RCV_FCN, CNTR_INVALID_VL, 0);
if (counter_select & CS_PORT_RCV_BECN)
write_dev_cntr(dd, C_DC_RCV_BCN, CNTR_INVALID_VL, 0);
/* ignore cs_port_xmit_time_cong for HFIs */ /* ignore cs_port_xmit_wasted_bw for now */ /* ignore cs_port_xmit_wait_data for now */ if (counter_select & CS_PORT_RCV_BUBBLE)
write_dev_cntr(dd, C_DC_RCV_BBL, CNTR_INVALID_VL, 0);
/* Only applicable for switch */ /* if (counter_select & CS_PORT_MARK_FECN) * write_csr(dd, DCC_PRF_PORT_MARK_FECN_CNT, 0);
*/
if (counter_select & CS_PORT_RCV_CONSTRAINT_ERRORS)
write_port_cntr(ppd, C_SW_RCV_CSTR_ERR, CNTR_INVALID_VL, 0);
/* ignore cs_port_rcv_switch_relay_errors for HFIs */ if (counter_select & CS_PORT_XMIT_DISCARDS)
write_port_cntr(ppd, C_SW_XMIT_DSCD, CNTR_INVALID_VL, 0);
if (counter_select & CS_PORT_XMIT_CONSTRAINT_ERRORS)
write_port_cntr(ppd, C_SW_XMIT_CSTR_ERR, CNTR_INVALID_VL, 0);
if (counter_select & CS_PORT_RCV_REMOTE_PHYSICAL_ERRORS)
write_dev_cntr(dd, C_DC_RMT_PHY_ERR, CNTR_INVALID_VL, 0);
if (counter_select & CS_LOCAL_LINK_INTEGRITY_ERRORS)
write_dev_cntr(dd, C_DC_RX_REPLAY, CNTR_INVALID_VL, 0);
/* * The bit set in the mask needs to be consistent with the port * the request came in on.
*/
port_mask = be64_to_cpu(req->port_select_mask[3]);
port_num = find_first_bit((unsignedlong *)&port_mask, sizeof(port_mask) * 8);
/* PortRcvErrorInfo */ if (error_info_select & ES_PORT_RCV_ERROR_INFO) /* turn off status bit */
dd->err_info_rcvport.status_and_code &= ~OPA_EI_STATUS_SMASK;
/* ExcessiverBufferOverrunInfo */ if (error_info_select & ES_EXCESSIVE_BUFFER_OVERRUN_INFO) /* * status bit is essentially kept in the h/w - bit 5 of * RCV_ERR_INFO
*/
write_csr(dd, RCV_ERR_INFO,
RCV_ERR_INFO_RCV_EXCESS_BUFFER_OVERRUN_SMASK);
if (error_info_select & ES_PORT_XMIT_CONSTRAINT_ERROR_INFO)
dd->err_info_xmit_constraint.status &= ~OPA_EI_STATUS_SMASK;
if (error_info_select & ES_PORT_RCV_CONSTRAINT_ERROR_INFO)
dd->err_info_rcv_constraint.status &= ~OPA_EI_STATUS_SMASK;
/* UncorrectableErrorInfo */ if (error_info_select & ES_UNCORRECTABLE_ERROR_INFO) /* turn off status bit */
dd->err_info_uncorrectable &= ~OPA_EI_STATUS_SMASK;
/* FMConfigErrorInfo */ if (error_info_select & ES_FM_CONFIG_ERROR_INFO) /* turn off status bit */
dd->err_info_fmconfig &= ~OPA_EI_STATUS_SMASK;
if (resp_len)
*resp_len += sizeof(*req);
return reply((struct ib_mad_hdr *)pmp);
}
struct opa_congestion_info_attr {
__be16 congestion_info;
u8 control_table_cap; /* Multiple of 64 entry unit CCTs */
u8 congestion_log_length;
} __packed;
/* * Apply congestion control information stored in the ppd to the * active structure.
*/ staticvoid apply_cc_state(struct hfi1_pportdata *ppd)
{ struct cc_state *old_cc_state, *new_cc_state;
new_cc_state = kzalloc(sizeof(*new_cc_state), GFP_KERNEL); if (!new_cc_state) return;
/* * Hold the lock for updating *and* to prevent ppd information * from changing during the update.
*/
spin_lock(&ppd->cc_state_lock);
old_cc_state = get_cc_state_protected(ppd); if (!old_cc_state) { /* never active, or shutting down */
spin_unlock(&ppd->cc_state_lock);
kfree(new_cc_state); return;
}
/* * Save details from packet into the ppd. Hold the cc_state_lock so * our information is consistent with anyone trying to apply the state.
*/
spin_lock(&ppd->cc_state_lock);
ppd->cc_sl_control_map = be32_to_cpu(p->control_map);
entries = ppd->congestion_entries; for (i = 0; i < OPA_MAX_SLS; i++) {
entries[i].ccti_increase = p->entries[i].ccti_increase;
entries[i].ccti_timer = be16_to_cpu(p->entries[i].ccti_timer);
entries[i].trigger_threshold =
p->entries[i].trigger_threshold;
entries[i].ccti_min = p->entries[i].ccti_min;
}
spin_unlock(&ppd->cc_state_lock);
/* now apply the information */
apply_cc_state(ppd);
cong_log->log_type = OPA_CC_LOG_TYPE_HFI;
cong_log->congestion_flags = 0;
cong_log->threshold_event_counter =
cpu_to_be16(ppd->threshold_event_counter);
memcpy(cong_log->threshold_cong_event_map,
ppd->threshold_cong_event_map, sizeof(cong_log->threshold_cong_event_map)); /* keep timestamp in units of 1.024 usec */
ts = ktime_get_ns() / 1024;
cong_log->current_time_stamp = cpu_to_be32(ts); for (i = 0; i < OPA_CONG_LOG_ELEMS; i++) { struct opa_hfi1_cong_log_event_internal *cce =
&ppd->cc_events[ppd->cc_mad_idx++]; if (ppd->cc_mad_idx == OPA_CONG_LOG_ELEMS)
ppd->cc_mad_idx = 0; /* * Entries which are older than twice the time * required to wrap the counter are supposed to * be zeroed (CA10-49 IBTA, release 1.2.1, V1).
*/ if ((ts - cce->timestamp) / 2 > U32_MAX) continue;
memcpy(cong_log->events[i].local_qp_cn_entry, &cce->lqpn, 3);
memcpy(cong_log->events[i].remote_qp_number_cn_entry,
&cce->rqpn, 3);
cong_log->events[i].sl_svc_type_cn_entry =
((cce->sl & 0x1f) << 3) | (cce->svc_type & 0x7);
cong_log->events[i].remote_lid_cn_entry =
cpu_to_be32(cce->rlid);
cong_log->events[i].timestamp_cn_entry =
cpu_to_be32(cce->timestamp);
}
/* * Reset threshold_cong_event_map, and threshold_event_counter * to 0 when log is read.
*/
memset(ppd->threshold_cong_event_map, 0x0, sizeof(ppd->threshold_cong_event_map));
ppd->threshold_event_counter = 0;
spin_unlock_irq(&ppd->cc_log_lock);
if (resp_len)
*resp_len += sizeof(struct opa_hfi1_cong_log);
/* return n_blocks, though the last block may not be full */ for (j = 0, i = sentry; i < eentry; j++, i++)
cc_table_attr->ccti_entries[j].entry =
cpu_to_be16(entries[i].entry);
/* * Save details from packet into the ppd. Hold the cc_state_lock so * our information is consistent with anyone trying to apply the state.
*/
spin_lock(&ppd->cc_state_lock);
ppd->total_cct_entry = ccti_limit + 1;
entries = ppd->ccti_entries; for (j = 0, i = sentry; i < eentry; j++, i++)
entries[i].entry = be16_to_cpu(p->ccti_entries[j].entry);
spin_unlock(&ppd->cc_state_lock);
/* now apply the information */
apply_cc_state(ppd);
/* * This pairs with the memory barrier in hfi1_start_led_override to * ensure that we read the correct state of LED beaconing represented * by led_override_timer_active
*/
smp_rmb();
is_beaconing_active = !!atomic_read(&ppd->led_override_timer_active);
p->rsvd_led_mask = cpu_to_be32(is_beaconing_active << OPA_LED_SHIFT);
if (resp_len)
*resp_len += sizeof(struct opa_led_info);
/* * opa_local_smp_check() should only be called on MADs for which * is_local_mad() returns true. It applies the SMP checks that are * specific to SMPs which are sent from, and destined to this node. * opa_local_smp_check() returns 0 if the SMP passes its checks, 1 * otherwise. * * SMPs which arrive from other nodes are instead checked by * opa_smp_check().
*/ staticint opa_local_smp_check(struct hfi1_ibport *ibp, conststruct ib_wc *in_wc)
{ struct hfi1_pportdata *ppd = ppd_from_ibp(ibp);
u16 pkey;
if (in_wc->pkey_index >= ARRAY_SIZE(ppd->pkeys)) return 1;
pkey = ppd->pkeys[in_wc->pkey_index]; /* * We need to do the "node-local" checks specified in OPAv1, * rev 0.90, section 9.10.26, which are: * - pkey is 0x7fff, or 0xffff * - Source QPN == 0 || Destination QPN == 0 * - the MAD header's management class is either * IB_MGMT_CLASS_SUBN_DIRECTED_ROUTE or * IB_MGMT_CLASS_SUBN_LID_ROUTED * - SLID != 0 * * However, we know (and so don't need to check again) that, * for local SMPs, the MAD stack passes MADs with: * - Source QPN of 0 * - MAD mgmt_class is IB_MGMT_CLASS_SUBN_DIRECTED_ROUTE * - SLID is either: OPA_LID_PERMISSIVE (0xFFFFFFFF), or * our own port's lid *
*/ if (pkey == LIM_MGMT_P_KEY || pkey == FULL_MGMT_P_KEY) return 0;
ingress_pkey_table_fail(ppd, pkey, in_wc->slid); return 1;
}
/** * hfi1_pkey_validation_pma - It validates PKEYs for incoming PMA MAD packets. * @ibp: IB port data * @in_mad: MAD packet with header and data * @in_wc: Work completion data such as source LID, port number, etc. * * These are all the possible logic rules for validating a pkey: * * a) If pkey neither FULL_MGMT_P_KEY nor LIM_MGMT_P_KEY, * and NOT self-originated packet: * Drop MAD packet as it should always be part of the * management partition unless it's a self-originated packet. * * b) If pkey_index -> FULL_MGMT_P_KEY, and LIM_MGMT_P_KEY in pkey table: * The packet is coming from a management node and the receiving node * is also a management node, so it is safe for the packet to go through. * * c) If pkey_index -> FULL_MGMT_P_KEY, and LIM_MGMT_P_KEY is NOT in pkey table: * Drop the packet as LIM_MGMT_P_KEY should always be in the pkey table. * It could be an FM misconfiguration. * * d) If pkey_index -> LIM_MGMT_P_KEY and FULL_MGMT_P_KEY is NOT in pkey table: * It is safe for the packet to go through since a non-management node is * talking to another non-management node. * * e) If pkey_index -> LIM_MGMT_P_KEY and FULL_MGMT_P_KEY in pkey table: * Drop the packet because a non-management node is talking to a * management node, and it could be an attack. * * For the implementation, these rules can be simplied to only checking * for (a) and (e). There's no need to check for rule (b) as * the packet doesn't need to be dropped. Rule (c) is not possible in * the driver as LIM_MGMT_P_KEY is always in the pkey table. * * Return: * 0 - pkey is okay, -EINVAL it's a bad pkey
*/ staticint hfi1_pkey_validation_pma(struct hfi1_ibport *ibp, conststruct opa_mad *in_mad, conststruct ib_wc *in_wc)
{
u16 pkey_value = hfi1_lookup_pkey_value(ibp, in_wc->pkey_index);
/* Rule (a) from above */ if (!is_local_mad(ibp, in_mad, in_wc) &&
pkey_value != LIM_MGMT_P_KEY &&
pkey_value != FULL_MGMT_P_KEY) return -EINVAL;
/* Rule (e) from above */ if (pkey_value == LIM_MGMT_P_KEY &&
is_full_mgmt_pkey_in_table(ibp)) return -EINVAL;
*out_mad = *in_mad;
data = opa_get_smp_data(smp);
data_size = (u32)opa_get_smp_data_size(smp);
am = be32_to_cpu(smp->attr_mod);
attr_id = smp->attr_id; if (smp->class_version != OPA_SM_CLASS_VERSION) {
smp->status |= IB_SMP_UNSUP_VERSION;
ret = reply((struct ib_mad_hdr *)smp); return ret;
}
ret = check_mkey(ibp, (struct ib_mad_hdr *)smp, mad_flags, smp->mkey,
smp->route.dr.dr_slid, smp->route.dr.return_path,
smp->hop_cnt); if (ret) {
u32 port_num = be32_to_cpu(smp->attr_mod);
/* * If this is a get/set portinfo, we already check the * M_Key if the MAD is for another port and the M_Key * is OK on the receiving port. This check is needed * to increment the error counters when the M_Key * fails to match on *both* ports.
*/ if (attr_id == IB_SMP_ATTR_PORT_INFO &&
(smp->method == IB_MGMT_METHOD_GET ||
smp->method == IB_MGMT_METHOD_SET) &&
port_num && port_num <= ibdev->phys_port_cnt &&
port != port_num)
(void)check_mkey(to_iport(ibdev, port_num),
(struct ib_mad_hdr *)smp, 0,
smp->mkey, smp->route.dr.dr_slid,
smp->route.dr.return_path,
smp->hop_cnt);
ret = IB_MAD_RESULT_FAILURE; return ret;
}
*resp_len = opa_get_smp_header_size(smp);
switch (smp->method) { case IB_MGMT_METHOD_GET: switch (attr_id) { default:
clear_opa_smp_data(smp);
ret = subn_get_opa_sma(attr_id, smp, am, data,
ibdev, port, resp_len,
data_size); break; case OPA_ATTRIB_ID_AGGREGATE:
ret = subn_get_opa_aggregate(smp, ibdev, port,
resp_len); break;
} break; case IB_MGMT_METHOD_SET: switch (attr_id) { default:
ret = subn_set_opa_sma(attr_id, smp, am, data,
ibdev, port, resp_len,
data_size, local_mad); break; case OPA_ATTRIB_ID_AGGREGATE:
ret = subn_set_opa_aggregate(smp, ibdev, port,
resp_len, local_mad); break;
} break; case IB_MGMT_METHOD_TRAP: case IB_MGMT_METHOD_REPORT: case IB_MGMT_METHOD_REPORT_RESP: case IB_MGMT_METHOD_GET_RESP: /* * The ib_mad module will call us to process responses * before checking for other consumers. * Just tell the caller to process it normally.
*/
ret = IB_MAD_RESULT_SUCCESS; break; case IB_MGMT_METHOD_TRAP_REPRESS:
subn_handle_opa_trap_repress(ibp, smp); /* Always successful */
ret = IB_MAD_RESULT_SUCCESS; break; default:
smp->status |= IB_SMP_UNSUP_METHOD;
ret = reply((struct ib_mad_hdr *)smp); break;
}
*out_mad = *in_mad; if (smp->class_version != 1) {
smp->status |= IB_SMP_UNSUP_VERSION;
ret = reply((struct ib_mad_hdr *)smp); return ret;
}
ret = check_mkey(ibp, (struct ib_mad_hdr *)smp, mad_flags,
smp->mkey, (__force __be32)smp->dr_slid,
smp->return_path, smp->hop_cnt); if (ret) {
u32 port_num = be32_to_cpu(smp->attr_mod);
/* * If this is a get/set portinfo, we already check the * M_Key if the MAD is for another port and the M_Key * is OK on the receiving port. This check is needed * to increment the error counters when the M_Key * fails to match on *both* ports.
*/ if (in_mad->mad_hdr.attr_id == IB_SMP_ATTR_PORT_INFO &&
(smp->method == IB_MGMT_METHOD_GET ||
smp->method == IB_MGMT_METHOD_SET) &&
port_num && port_num <= ibdev->phys_port_cnt &&
port != port_num)
(void)check_mkey(to_iport(ibdev, port_num),
(struct ib_mad_hdr *)smp, 0,
smp->mkey,
(__force __be32)smp->dr_slid,
smp->return_path, smp->hop_cnt);
ret = IB_MAD_RESULT_FAILURE; return ret;
}
switch (smp->method) { case IB_MGMT_METHOD_GET: switch (smp->attr_id) { case IB_SMP_ATTR_NODE_INFO:
ret = subn_get_nodeinfo(smp, ibdev, port); break; default:
smp->status |= IB_SMP_UNSUP_METH_ATTR;
ret = reply((struct ib_mad_hdr *)smp); break;
} break;
}
*out_mad = *in_mad; if (pmp->mad_hdr.class_version != 1) {
pmp->mad_hdr.status |= IB_SMP_UNSUP_VERSION;
ret = reply((struct ib_mad_hdr *)pmp); return ret;
}
switch (pmp->mad_hdr.method) { case IB_MGMT_METHOD_GET: switch (pmp->mad_hdr.attr_id) { case IB_PMA_PORT_COUNTERS:
ret = pma_get_ib_portcounters(pmp, ibdev, port); break; case IB_PMA_PORT_COUNTERS_EXT:
ret = pma_get_ib_portcounters_ext(pmp, ibdev, port); break; case IB_PMA_CLASS_PORT_INFO:
cpi->capability_mask = IB_PMA_CLASS_CAP_EXT_WIDTH;
ret = reply((struct ib_mad_hdr *)pmp); break; default:
pmp->mad_hdr.status |= IB_SMP_UNSUP_METH_ATTR;
ret = reply((struct ib_mad_hdr *)pmp); break;
} break;
case IB_MGMT_METHOD_SET: if (pmp->mad_hdr.attr_id) {
pmp->mad_hdr.status |= IB_SMP_UNSUP_METH_ATTR;
ret = reply((struct ib_mad_hdr *)pmp);
} break;
case IB_MGMT_METHOD_TRAP: case IB_MGMT_METHOD_GET_RESP: /* * The ib_mad module will call us to process responses * before checking for other consumers. * Just tell the caller to process it normally.
*/
ret = IB_MAD_RESULT_SUCCESS; break;
switch (pmp->mad_hdr.method) { case IB_MGMT_METHOD_GET: switch (pmp->mad_hdr.attr_id) { case IB_PMA_CLASS_PORT_INFO:
ret = pma_get_opa_classportinfo(pmp, ibdev, resp_len); break; case OPA_PM_ATTRIB_ID_PORT_STATUS:
ret = pma_get_opa_portstatus(pmp, ibdev, port,
resp_len); break; case OPA_PM_ATTRIB_ID_DATA_PORT_COUNTERS:
ret = pma_get_opa_datacounters(pmp, ibdev, port,
resp_len); break; case OPA_PM_ATTRIB_ID_ERROR_PORT_COUNTERS:
ret = pma_get_opa_porterrors(pmp, ibdev, port,
resp_len); break; case OPA_PM_ATTRIB_ID_ERROR_INFO:
ret = pma_get_opa_errorinfo(pmp, ibdev, port,
resp_len); break; default:
pmp->mad_hdr.status |= IB_SMP_UNSUP_METH_ATTR;
ret = reply((struct ib_mad_hdr *)pmp); break;
} break;
case IB_MGMT_METHOD_SET: switch (pmp->mad_hdr.attr_id) { case OPA_PM_ATTRIB_ID_CLEAR_PORT_STATUS:
ret = pma_set_opa_portstatus(pmp, ibdev, port,
resp_len); break; case OPA_PM_ATTRIB_ID_ERROR_INFO:
ret = pma_set_opa_errorinfo(pmp, ibdev, port,
resp_len); break; default:
pmp->mad_hdr.status |= IB_SMP_UNSUP_METH_ATTR;
ret = reply((struct ib_mad_hdr *)pmp); break;
} break;
case IB_MGMT_METHOD_TRAP: case IB_MGMT_METHOD_GET_RESP: /* * The ib_mad module will call us to process responses * before checking for other consumers. * Just tell the caller to process it normally.
*/
ret = IB_MAD_RESULT_SUCCESS; break;
switch (in_mad->mad_hdr.mgmt_class) { case IB_MGMT_CLASS_SUBN_DIRECTED_ROUTE: case IB_MGMT_CLASS_SUBN_LID_ROUTED:
ret = process_subn(ibdev, mad_flags, port, in_mad, out_mad); break; case IB_MGMT_CLASS_PERF_MGMT:
ret = process_perf(ibdev, port, in_mad, out_mad); break; default:
ret = IB_MAD_RESULT_SUCCESS; break;
}
return ret;
}
/** * hfi1_process_mad - process an incoming MAD packet * @ibdev: the infiniband device this packet came in on * @mad_flags: MAD flags * @port: the port number this packet came in on * @in_wc: the work completion entry for this packet * @in_grh: the global route header for this packet * @in_mad: the incoming MAD * @out_mad: any outgoing MAD reply * @out_mad_size: size of the outgoing MAD reply * @out_mad_pkey_index: used to apss back the packet key index * * Returns IB_MAD_RESULT_SUCCESS if this is a MAD that we are not * interested in processing. * * Note that the verbs framework has already done the MAD sanity checks, * and hop count/pointer updating for IB_MGMT_CLASS_SUBN_DIRECTED_ROUTE * MADs. * * This is called by the ib_mad module.
*/ int hfi1_process_mad(struct ib_device *ibdev, int mad_flags, u32 port, conststruct ib_wc *in_wc, conststruct ib_grh *in_grh, conststruct ib_mad *in_mad, struct ib_mad *out_mad,
size_t *out_mad_size, u16 *out_mad_pkey_index)
{ switch (in_mad->mad_hdr.base_version) { case OPA_MGMT_BASE_VERSION: return hfi1_process_opa_mad(ibdev, mad_flags, port,
in_wc, in_grh,
(struct opa_mad *)in_mad,
(struct opa_mad *)out_mad,
out_mad_size,
out_mad_pkey_index); case IB_MGMT_BASE_VERSION: return hfi1_process_ib_mad(ibdev, mad_flags, port, in_wc,
in_grh, in_mad, out_mad); default: break;
}
return IB_MAD_RESULT_FAILURE;
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.70 Sekunden
(vorverarbeitet am 2026-04-29)
¤
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.