/* * Copyright 2019 Advanced Micro Devices, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE.
*/
/* * DO NOT use these for err/warn/info/debug messages. * Use dev_err, dev_warn, dev_info and dev_dbg instead. * They are more MGPU friendly.
*/ #undef pr_err #undef pr_warn #undef pr_info #undef pr_debug
switch (amdgpu_ip_version(adev, MP1_HWIP, 0)) { case IP_VERSION(11, 0, 0):
smu->smc_driver_if_version = SMU11_DRIVER_IF_VERSION_NV10; break; case IP_VERSION(11, 0, 9):
smu->smc_driver_if_version = SMU11_DRIVER_IF_VERSION_NV12; break; case IP_VERSION(11, 0, 5):
smu->smc_driver_if_version = SMU11_DRIVER_IF_VERSION_NV14; break; case IP_VERSION(11, 0, 7):
smu->smc_driver_if_version = SMU11_DRIVER_IF_VERSION_Sienna_Cichlid; break; case IP_VERSION(11, 0, 11):
smu->smc_driver_if_version = SMU11_DRIVER_IF_VERSION_Navy_Flounder; break; case IP_VERSION(11, 5, 0): case IP_VERSION(11, 5, 2):
smu->smc_driver_if_version = SMU11_DRIVER_IF_VERSION_VANGOGH; break; case IP_VERSION(11, 0, 12):
smu->smc_driver_if_version = SMU11_DRIVER_IF_VERSION_Dimgrey_Cavefish; break; case IP_VERSION(11, 0, 13):
smu->smc_driver_if_version = SMU11_DRIVER_IF_VERSION_Beige_Goby; break; case IP_VERSION(11, 0, 8):
smu->smc_driver_if_version = SMU11_DRIVER_IF_VERSION_Cyan_Skillfish; break; case IP_VERSION(11, 0, 2):
smu->smc_driver_if_version = SMU11_DRIVER_IF_VERSION_ARCT; break; default:
dev_err(smu->adev->dev, "smu unsupported IP version: 0x%x.\n",
amdgpu_ip_version(adev, MP1_HWIP, 0));
smu->smc_driver_if_version = SMU11_DRIVER_IF_VERSION_INV; break;
}
/* * 1. if_version mismatch is not critical as our fw is designed * to be backward compatible. * 2. New fw usually brings some optimizations. But that's visible * only on the paired driver. * Considering above, we just leave user a verbal message instead * of halt driver loading.
*/ if (if_version != smu->smc_driver_if_version) {
dev_info(smu->adev->dev, "smu driver if version = 0x%08x, smu fw if version = 0x%08x, " "smu fw program = %d, version = 0x%08x (%d.%d.%d)\n",
smu->smc_driver_if_version, if_version,
smu_program, smu_version, smu_major, smu_minor, smu_debug);
dev_info(smu->adev->dev, "SMU driver if version not matched\n");
}
if (!amdgpu_sriov_vf(adev)) {
hdr = (conststruct smc_firmware_header_v1_0 *) adev->pm.fw->data;
version_major = le16_to_cpu(hdr->header.header_version_major);
version_minor = le16_to_cpu(hdr->header.header_version_minor); if (version_major == 2 && smu->smu_table.boot_values.pp_table_id > 0) {
dev_info(adev->dev, "use driver provided pptable %d\n", smu->smu_table.boot_values.pp_table_id); switch (version_minor) { case 0:
ret = smu_v11_0_set_pptable_v2_0(smu, &table, &size); break; case 1:
ret = smu_v11_0_set_pptable_v2_1(smu, &table, &size,
smu->smu_table.boot_values.pp_table_id); break; default:
ret = -EINVAL; break;
} if (ret) return ret; goto out;
}
}
dev_info(adev->dev, "use vbios provided pptable\n");
index = get_index_into_master_table(atom_master_list_of_data_tables_v2_1,
powerplayinfo);
ret = amdgpu_atombios_get_data_table(adev, index, &atom_table_size, &frev, &crev,
(uint8_t **)&table); if (ret) return ret;
size = atom_table_size;
out: if (!smu->smu_table.power_play_table)
smu->smu_table.power_play_table = table; if (!smu->smu_table.power_play_table_size)
smu->smu_table.power_play_table_size = size;
return 0;
}
int smu_v11_0_init_smc_tables(struct smu_context *smu)
{ struct smu_table_context *smu_table = &smu->smu_table; struct smu_table *tables = smu_table->tables; int ret = 0;
smu_table->driver_pptable =
kzalloc(tables[SMU_TABLE_PPTABLE].size, GFP_KERNEL); if (!smu_table->driver_pptable) {
ret = -ENOMEM; goto err0_out;
}
smu_table->max_sustainable_clocks =
kzalloc(sizeof(struct smu_11_0_max_sustainable_clocks), GFP_KERNEL); if (!smu_table->max_sustainable_clocks) {
ret = -ENOMEM; goto err1_out;
}
/* Arcturus does not support OVERDRIVE */ if (tables[SMU_TABLE_OVERDRIVE].size) {
smu_table->overdrive_table =
kzalloc(tables[SMU_TABLE_OVERDRIVE].size, GFP_KERNEL); if (!smu_table->overdrive_table) {
ret = -ENOMEM; goto err2_out;
}
smu_table->boot_overdrive_table =
kzalloc(tables[SMU_TABLE_OVERDRIVE].size, GFP_KERNEL); if (!smu_table->boot_overdrive_table) {
ret = -ENOMEM; goto err3_out;
}
smu_table->user_overdrive_table =
kzalloc(tables[SMU_TABLE_OVERDRIVE].size, GFP_KERNEL); if (!smu_table->user_overdrive_table) {
ret = -ENOMEM; goto err4_out;
}
ret = smu_cmn_send_smc_msg_with_param(smu,
SMU_MSG_SetSystemVirtualDramAddrHigh,
address_high,
NULL); if (ret) return ret;
ret = smu_cmn_send_smc_msg_with_param(smu,
SMU_MSG_SetSystemVirtualDramAddrLow,
address_low,
NULL); if (ret) return ret;
ret = smu_cmn_send_smc_msg_with_param(smu, SMU_MSG_DramLogSetDramAddrHigh,
address_high, NULL); if (ret) return ret;
ret = smu_cmn_send_smc_msg_with_param(smu, SMU_MSG_DramLogSetDramAddrLow,
address_low, NULL); if (ret) return ret;
ret = smu_cmn_send_smc_msg_with_param(smu, SMU_MSG_DramLogSetDramSize,
(uint32_t)memory_pool->size, NULL); if (ret) return ret;
return ret;
}
int smu_v11_0_set_min_deep_sleep_dcefclk(struct smu_context *smu, uint32_t clk)
{ int ret;
ret = smu_cmn_send_smc_msg_with_param(smu,
SMU_MSG_SetMinDeepSleepDcefclk, clk, NULL); if (ret)
dev_err(smu->adev->dev, "SMU11 attempt to set divider for DCEFCLK Failed!");
return ret;
}
int smu_v11_0_set_driver_table_location(struct smu_context *smu)
{ struct smu_table *driver_table = &smu->smu_table.driver_table; int ret = 0;
if (driver_table->mc_address) {
ret = smu_cmn_send_smc_msg_with_param(smu,
SMU_MSG_SetDriverDramAddrHigh,
upper_32_bits(driver_table->mc_address),
NULL); if (!ret)
ret = smu_cmn_send_smc_msg_with_param(smu,
SMU_MSG_SetDriverDramAddrLow,
lower_32_bits(driver_table->mc_address),
NULL);
}
return ret;
}
int smu_v11_0_set_tool_table_location(struct smu_context *smu)
{ int ret = 0; struct smu_table *tool_table = &smu->smu_table.tables[SMU_TABLE_PMSTATUSLOG];
if (tool_table->mc_address) {
ret = smu_cmn_send_smc_msg_with_param(smu,
SMU_MSG_SetToolsDramAddrHigh,
upper_32_bits(tool_table->mc_address),
NULL); if (!ret)
ret = smu_cmn_send_smc_msg_with_param(smu,
SMU_MSG_SetToolsDramAddrLow,
lower_32_bits(tool_table->mc_address),
NULL);
}
ret = smu_cmn_send_smc_msg_with_param(smu, SMU_MSG_GetDcModeMaxDpmFreq,
clk_id << 16, clock); if (ret) {
dev_err(smu->adev->dev, "[GetMaxSustainableClock] Failed to get max DC clock from SMC!"); return ret;
}
if (*clock != 0) return 0;
/* if DC limit is zero, return AC limit */
ret = smu_cmn_send_smc_msg_with_param(smu, SMU_MSG_GetMaxDpmFreq,
clk_id << 16, clock); if (ret) {
dev_err(smu->adev->dev, "[GetMaxSustainableClock] failed to get max AC clock from SMC!"); return ret;
}
return 0;
}
int smu_v11_0_init_max_sustainable_clocks(struct smu_context *smu)
{ struct smu_11_0_max_sustainable_clocks *max_sustainable_clocks =
smu->smu_table.max_sustainable_clocks; int ret = 0;
if (smu_cmn_feature_is_enabled(smu, SMU_FEATURE_DPM_UCLK_BIT)) {
ret = smu_v11_0_get_max_sustainable_clock(smu,
&(max_sustainable_clocks->uclock),
SMU_UCLK); if (ret) {
dev_err(smu->adev->dev, "[%s] failed to get max UCLK from SMC!",
__func__); return ret;
}
}
if (smu_cmn_feature_is_enabled(smu, SMU_FEATURE_DPM_SOCCLK_BIT)) {
ret = smu_v11_0_get_max_sustainable_clock(smu,
&(max_sustainable_clocks->soc_clock),
SMU_SOCCLK); if (ret) {
dev_err(smu->adev->dev, "[%s] failed to get max SOCCLK from SMC!",
__func__); return ret;
}
}
if (smu_cmn_feature_is_enabled(smu, SMU_FEATURE_DPM_DCEFCLK_BIT)) {
ret = smu_v11_0_get_max_sustainable_clock(smu,
&(max_sustainable_clocks->dcef_clock),
SMU_DCEFCLK); if (ret) {
dev_err(smu->adev->dev, "[%s] failed to get max DCEFCLK from SMC!",
__func__); return ret;
}
ret = smu_v11_0_get_max_sustainable_clock(smu,
&(max_sustainable_clocks->display_clock),
SMU_DISPCLK); if (ret) {
dev_err(smu->adev->dev, "[%s] failed to get max DISPCLK from SMC!",
__func__); return ret;
}
ret = smu_v11_0_get_max_sustainable_clock(smu,
&(max_sustainable_clocks->phy_clock),
SMU_PHYCLK); if (ret) {
dev_err(smu->adev->dev, "[%s] failed to get max PHYCLK from SMC!",
__func__); return ret;
}
ret = smu_v11_0_get_max_sustainable_clock(smu,
&(max_sustainable_clocks->pixel_clock),
SMU_PIXCLK); if (ret) {
dev_err(smu->adev->dev, "[%s] failed to get max PIXCLK from SMC!",
__func__); return ret;
}
}
if (max_sustainable_clocks->soc_clock < max_sustainable_clocks->uclock)
max_sustainable_clocks->uclock = max_sustainable_clocks->soc_clock;
return 0;
}
int smu_v11_0_get_current_power_limit(struct smu_context *smu,
uint32_t *power_limit)
{ int power_src; int ret = 0;
if (!smu_cmn_feature_is_enabled(smu, SMU_FEATURE_PPT_BIT)) return -EINVAL;
/* * BIT 24-31: ControllerId (only PPT0 is supported for now) * BIT 16-23: PowerSource
*/
ret = smu_cmn_send_smc_msg_with_param(smu,
SMU_MSG_GetPptLimit,
(0 << 24) | (power_src << 16),
power_limit); if (ret)
dev_err(smu->adev->dev, "[%s] get PPT limit failed!", __func__);
return ret;
}
int smu_v11_0_set_power_limit(struct smu_context *smu, enum smu_ppt_limit_type limit_type,
uint32_t limit)
{ int power_src; int ret = 0;
uint32_t limit_param;
if (limit_type != SMU_DEFAULT_PPT_LIMIT) return -EINVAL;
if (!smu_cmn_feature_is_enabled(smu, SMU_FEATURE_PPT_BIT)) {
dev_err(smu->adev->dev, "Setting new power limit is not supported!\n"); return -EOPNOTSUPP;
}
int smu_v11_0_enable_thermal_alert(struct smu_context *smu)
{ int ret = 0;
if (smu->smu_table.thermal_controller_type) {
ret = amdgpu_irq_get(smu->adev, &smu->irq_source, 0); if (ret) return ret;
}
/* * After init there might have been missed interrupts triggered * before driver registers for interrupt (Ex. AC/DC).
*/ return smu_v11_0_process_pending_interrupt(smu);
}
int smu_v11_0_disable_thermal_alert(struct smu_context *smu)
{ return amdgpu_irq_put(smu->adev, &smu->irq_source, 0);
}
int smu_v11_0_gfx_off_control(struct smu_context *smu, bool enable)
{ int ret = 0; struct amdgpu_device *adev = smu->adev;
switch (amdgpu_ip_version(adev, MP1_HWIP, 0)) { case IP_VERSION(11, 0, 0): case IP_VERSION(11, 0, 5): case IP_VERSION(11, 0, 9): case IP_VERSION(11, 0, 7): case IP_VERSION(11, 0, 11): case IP_VERSION(11, 0, 12): case IP_VERSION(11, 0, 13): case IP_VERSION(11, 5, 0): case IP_VERSION(11, 5, 2): if (!(adev->pm.pp_feature & PP_GFXOFF_MASK)) return 0; if (enable)
ret = smu_cmn_send_smc_msg(smu, SMU_MSG_AllowGfxOff, NULL); else
ret = smu_cmn_send_smc_msg(smu, SMU_MSG_DisallowGfxOff, NULL); break; default: break;
}
staticint
smu_v11_0_auto_fan_control(struct smu_context *smu, bool auto_fan_control)
{ int ret = 0;
if (!smu_cmn_feature_is_supported(smu, SMU_FEATURE_FAN_CONTROL_BIT)) return 0;
ret = smu_cmn_feature_set_enabled(smu, SMU_FEATURE_FAN_CONTROL_BIT, auto_fan_control); if (ret)
dev_err(smu->adev->dev, "[%s]%s smc FAN CONTROL feature failed!",
__func__, (auto_fan_control ? "Start" : "Stop"));
int smu_v11_0_set_fan_speed_rpm(struct smu_context *smu,
uint32_t speed)
{ struct amdgpu_device *adev = smu->adev; /* * crystal_clock_freq used for fan speed rpm calculation is * always 25Mhz. So, hardcode it as 2500(in 10K unit).
*/
uint32_t crystal_clock_freq = 2500;
uint32_t tach_period;
if (!speed || speed > UINT_MAX/8) return -EINVAL; /* * To prevent from possible overheat, some ASICs may have requirement * for minimum fan speed: * - For some NV10 SKU, the fan speed cannot be set lower than * 700 RPM. * - For some Sienna Cichlid SKU, the fan speed cannot be set * lower than 500 RPM.
*/
tach_period = 60 * crystal_clock_freq * 10000 / (8 * speed);
WREG32_SOC15(THM, 0, mmCG_TACH_CTRL,
REG_SET_FIELD(RREG32_SOC15(THM, 0, mmCG_TACH_CTRL),
CG_TACH_CTRL, TARGET_PERIOD,
tach_period));
/* * For pre Sienna Cichlid ASICs, the 0 RPM may be not correctly * detected via register retrieving. To workaround this, we will * report the fan speed as 0 PWM if user just requested such.
*/ if ((smu->user_dpm_profile.flags & SMU_CUSTOM_FAN_SPEED_PWM)
&& !smu->user_dpm_profile.fan_speed_pwm) {
*speed = 0; return 0;
}
/* * For pre Sienna Cichlid ASICs, the 0 RPM may be not correctly * detected via register retrieving. To workaround this, we will * report the fan speed as 0 RPM if user just requested such.
*/ if ((smu->user_dpm_profile.flags & SMU_CUSTOM_FAN_SPEED_RPM)
&& !smu->user_dpm_profile.fan_speed_rpm) {
*speed = 0; return 0;
}
tach_status = RREG32_SOC15(THM, 0, mmCG_TACH_STATUS); if (tach_status) {
do_div(tmp64, tach_status);
*speed = (uint32_t)tmp64;
} else {
dev_warn_once(adev->dev, "Got zero output on CG_TACH_STATUS reading!\n");
*speed = 0;
}
return 0;
}
int
smu_v11_0_set_fan_control_mode(struct smu_context *smu,
uint32_t mode)
{ int ret = 0;
switch (mode) { case AMD_FAN_CTRL_NONE:
ret = smu_v11_0_auto_fan_control(smu, 0); if (!ret)
ret = smu_v11_0_set_fan_speed_pwm(smu, 255); break; case AMD_FAN_CTRL_MANUAL:
ret = smu_v11_0_auto_fan_control(smu, 0); break; case AMD_FAN_CTRL_AUTO:
ret = smu_v11_0_auto_fan_control(smu, 1); break; default: break;
}
if (ret) {
dev_err(smu->adev->dev, "[%s]Set fan control mode failed!", __func__); return -EINVAL;
}
staticint smu_v11_0_irq_process(struct amdgpu_device *adev, struct amdgpu_irq_src *source, struct amdgpu_iv_entry *entry)
{ struct smu_context *smu = adev->powerplay.pp_handle;
uint32_t client_id = entry->client_id;
uint32_t src_id = entry->src_id; /* * ctxid is used to distinguish different * events for SMCToHost interrupt.
*/
uint32_t ctxid = entry->src_data[0];
uint32_t data;
if (client_id == SOC15_IH_CLIENTID_THM) { switch (src_id) { case THM_11_0__SRCID__THM_DIG_THERM_L2H:
schedule_delayed_work(&smu->swctf_delayed_work,
msecs_to_jiffies(AMDGPU_SWCTF_EXTRA_DELAY)); break; case THM_11_0__SRCID__THM_DIG_THERM_H2L:
dev_emerg(adev->dev, "ERROR: GPU under temperature range detected\n"); break; default:
dev_emerg(adev->dev, "ERROR: GPU under temperature range unknown src id (%d)\n",
src_id); break;
}
} elseif (client_id == SOC15_IH_CLIENTID_ROM_SMUIO) {
dev_emerg(adev->dev, "ERROR: GPU HW Critical Temperature Fault(aka CTF) detected!\n"); /* * HW CTF just occurred. Shutdown to prevent further damage.
*/
dev_emerg(adev->dev, "ERROR: System is going to shutdown due to GPU HW CTF!\n");
orderly_poweroff(true);
} elseif (client_id == SOC15_IH_CLIENTID_MP1) { if (src_id == SMU_IH_INTERRUPT_ID_TO_DRIVER) { /* ACK SMUToHost interrupt */
data = RREG32_SOC15(MP1, 0, mmMP1_SMN_IH_SW_INT_CTRL);
data = REG_SET_FIELD(data, MP1_SMN_IH_SW_INT_CTRL, INT_ACK, 1);
WREG32_SOC15(MP1, 0, mmMP1_SMN_IH_SW_INT_CTRL, data);
switch (ctxid) { case SMU_IH_INTERRUPT_CONTEXT_ID_AC:
dev_dbg(adev->dev, "Switched to AC mode!\n");
schedule_work(&smu->interrupt_work);
adev->pm.ac_power = true; break; case SMU_IH_INTERRUPT_CONTEXT_ID_DC:
dev_dbg(adev->dev, "Switched to DC mode!\n");
schedule_work(&smu->interrupt_work);
adev->pm.ac_power = false; break; case SMU_IH_INTERRUPT_CONTEXT_ID_THERMAL_THROTTLING: /* * Increment the throttle interrupt counter
*/
atomic64_inc(&smu->throttle_int_counter);
if (!atomic_read(&adev->throttling_logging_enabled)) return 0;
if (__ratelimit(&adev->throttling_logging_rs))
schedule_work(&smu->throttling_logging_work);
break; default:
dev_dbg(adev->dev, "Unhandled context id %d from client:%d!\n",
ctxid, client_id); break;
}
}
}
int smu_v11_0_get_bamaco_support(struct smu_context *smu)
{ struct smu_baco_context *smu_baco = &smu->smu_baco; int bamaco_support = 0;
if (amdgpu_sriov_vf(smu->adev) || !smu_baco->platform_support) return 0;
if (smu_baco->maco_support)
bamaco_support |= MACO_SUPPORT;
/* return true if ASIC is in BACO state already */ if (smu_v11_0_baco_get_state(smu) == SMU_BACO_STATE_ENTER) return bamaco_support |= BACO_SUPPORT;
/* Arcturus does not support this bit mask */ if (smu_cmn_feature_is_supported(smu, SMU_FEATURE_BACO_BIT) &&
!smu_cmn_feature_is_enabled(smu, SMU_FEATURE_BACO_BIT)) return 0;
int smu_v11_0_baco_enter(struct smu_context *smu)
{ int ret = 0;
ret = smu_v11_0_baco_set_state(smu, SMU_BACO_STATE_ENTER); if (ret) return ret;
msleep(10);
return ret;
}
int smu_v11_0_baco_exit(struct smu_context *smu)
{ int ret;
ret = smu_v11_0_baco_set_state(smu, SMU_BACO_STATE_EXIT); if (!ret) { /* * Poll BACO exit status to ensure FW has completed * BACO exit process to avoid timing issues.
*/
smu_v11_0_poll_baco_exit(smu);
}
return ret;
}
int smu_v11_0_mode1_reset(struct smu_context *smu)
{ int ret = 0;
ret = smu_cmn_send_smc_msg(smu, SMU_MSG_Mode1Reset, NULL); if (!ret)
msleep(SMU11_MODE1_RESET_WAIT_TIME_IN_MS);
return ret;
}
int smu_v11_0_handle_passthrough_sbr(struct smu_context *smu, bool enable)
{ int ret = 0;
ret = smu_cmn_send_smc_msg_with_param(smu, SMU_MSG_LightSBR, enable ? 1 : 0, NULL);
return ret;
}
int smu_v11_0_get_dpm_ultimate_freq(struct smu_context *smu, enum smu_clk_type clk_type,
uint32_t *min, uint32_t *max)
{ int ret = 0, clk_id = 0;
uint32_t param = 0;
uint32_t clock_limit;
if (!smu_cmn_clk_dpm_is_enabled(smu, clk_type)) { switch (clk_type) { case SMU_MCLK: case SMU_UCLK:
clock_limit = smu->smu_table.boot_values.uclk; break; case SMU_GFXCLK: case SMU_SCLK:
clock_limit = smu->smu_table.boot_values.gfxclk; break; case SMU_SOCCLK:
clock_limit = smu->smu_table.boot_values.socclk; break; default:
clock_limit = 0; break;
}
/* clock in Mhz unit */ if (min)
*min = clock_limit / 100; if (max)
*max = clock_limit / 100;
int smu_v11_0_set_single_dpm_table(struct smu_context *smu, enum smu_clk_type clk_type, struct smu_11_0_dpm_table *single_dpm_table)
{ int ret = 0;
uint32_t clk; int i;
ret = smu_v11_0_get_dpm_level_count(smu,
clk_type,
&single_dpm_table->count); if (ret) {
dev_err(smu->adev->dev, "[%s] failed to get dpm levels!\n", __func__); return ret;
}
for (i = 0; i < single_dpm_table->count; i++) {
ret = smu_v11_0_get_dpm_freq_by_index(smu,
clk_type,
i,
&clk); if (ret) {
dev_err(smu->adev->dev, "[%s] failed to get dpm freq by index!\n", __func__); return ret;
}
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.