Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/media/usb/airspy/   (Linux Kernel Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 90 B image not shown  

Nach dem Mausklick ist die Projektion eines vierdimensionalen Würfels zu sehen rss.c   Sprache: unbekannt

 
// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
// Copyright (c) 2021, NVIDIA CORPORATION & AFFILIATES.

#include "rss.h"

#define mlx5e_rss_warn(__dev, format, ...)   \
 dev_warn((__dev)->device, "%s:%d:(pid %d): " format, \
   __func__, __LINE__, current->pid,  \
   ##__VA_ARGS__)

static const struct mlx5e_rss_params_traffic_type rss_default_config[MLX5E_NUM_INDIR_TIRS] = {
 [MLX5_TT_IPV4_TCP] = {
  .l3_prot_type = MLX5_L3_PROT_TYPE_IPV4,
  .l4_prot_type = MLX5_L4_PROT_TYPE_TCP,
  .rx_hash_fields = MLX5_HASH_IP_L4PORTS,
 },
 [MLX5_TT_IPV6_TCP] = {
  .l3_prot_type = MLX5_L3_PROT_TYPE_IPV6,
  .l4_prot_type = MLX5_L4_PROT_TYPE_TCP,
  .rx_hash_fields = MLX5_HASH_IP_L4PORTS,
 },
 [MLX5_TT_IPV4_UDP] = {
  .l3_prot_type = MLX5_L3_PROT_TYPE_IPV4,
  .l4_prot_type = MLX5_L4_PROT_TYPE_UDP,
  .rx_hash_fields = MLX5_HASH_IP_L4PORTS,
 },
 [MLX5_TT_IPV6_UDP] = {
  .l3_prot_type = MLX5_L3_PROT_TYPE_IPV6,
  .l4_prot_type = MLX5_L4_PROT_TYPE_UDP,
  .rx_hash_fields = MLX5_HASH_IP_L4PORTS,
 },
 [MLX5_TT_IPV4_IPSEC_AH] = {
  .l3_prot_type = MLX5_L3_PROT_TYPE_IPV4,
  .l4_prot_type = 0,
  .rx_hash_fields = MLX5_HASH_IP_IPSEC_SPI,
 },
 [MLX5_TT_IPV6_IPSEC_AH] = {
  .l3_prot_type = MLX5_L3_PROT_TYPE_IPV6,
  .l4_prot_type = 0,
  .rx_hash_fields = MLX5_HASH_IP_IPSEC_SPI,
 },
 [MLX5_TT_IPV4_IPSEC_ESP] = {
  .l3_prot_type = MLX5_L3_PROT_TYPE_IPV4,
  .l4_prot_type = 0,
  .rx_hash_fields = MLX5_HASH_IP_IPSEC_SPI,
 },
 [MLX5_TT_IPV6_IPSEC_ESP] = {
  .l3_prot_type = MLX5_L3_PROT_TYPE_IPV6,
  .l4_prot_type = 0,
  .rx_hash_fields = MLX5_HASH_IP_IPSEC_SPI,
 },
 [MLX5_TT_IPV4] = {
  .l3_prot_type = MLX5_L3_PROT_TYPE_IPV4,
  .l4_prot_type = 0,
  .rx_hash_fields = MLX5_HASH_IP,
 },
 [MLX5_TT_IPV6] = {
  .l3_prot_type = MLX5_L3_PROT_TYPE_IPV6,
  .l4_prot_type = 0,
  .rx_hash_fields = MLX5_HASH_IP,
 },
};

struct mlx5e_rss_params_traffic_type
mlx5e_rss_get_default_tt_config(enum mlx5_traffic_types tt)
{
 return rss_default_config[tt];
}

struct mlx5e_rss {
 struct mlx5e_rss_params_hash hash;
 struct mlx5e_rss_params_indir indir;
 u32 rx_hash_fields[MLX5E_NUM_INDIR_TIRS];
 struct mlx5e_tir *tir[MLX5E_NUM_INDIR_TIRS];
 struct mlx5e_tir *inner_tir[MLX5E_NUM_INDIR_TIRS];
 struct mlx5e_rqt rqt;
 struct mlx5_core_dev *mdev; /* primary */
 u32 drop_rqn;
 bool inner_ft_support;
 bool enabled;
 refcount_t refcnt;
};

bool mlx5e_rss_get_inner_ft_support(struct mlx5e_rss *rss)
{
 return rss->inner_ft_support;
}

void mlx5e_rss_params_indir_modify_actual_size(struct mlx5e_rss *rss, u32 num_channels)
{
 rss->indir.actual_table_size = mlx5e_rqt_size(rss->mdev, num_channels);
}

int mlx5e_rss_params_indir_init(struct mlx5e_rss_params_indir *indir, struct mlx5_core_dev *mdev,
    u32 actual_table_size, u32 max_table_size)
{
 indir->table = kvmalloc_array(max_table_size, sizeof(*indir->table), GFP_KERNEL);
 if (!indir->table)
  return -ENOMEM;

 indir->max_table_size = max_table_size;
 indir->actual_table_size = actual_table_size;

 return 0;
}

void mlx5e_rss_params_indir_cleanup(struct mlx5e_rss_params_indir *indir)
{
 kvfree(indir->table);
}

static int mlx5e_rss_copy(struct mlx5e_rss *to, const struct mlx5e_rss *from)
{
 u32 *dst_indir_table;

 if (to->indir.actual_table_size != from->indir.actual_table_size ||
     to->indir.max_table_size != from->indir.max_table_size) {
  mlx5e_rss_warn(to->mdev,
          "Failed to copy RSS due to size mismatch, src (actual %u, max %u) != dst (actual %u, max %u)\n",
          from->indir.actual_table_size, from->indir.max_table_size,
          to->indir.actual_table_size, to->indir.max_table_size);
  return -EINVAL;
 }

 dst_indir_table = to->indir.table;
 *to = *from;
 to->indir.table = dst_indir_table;
 memcpy(to->indir.table, from->indir.table,
        from->indir.actual_table_size * sizeof(*from->indir.table));
 return 0;
}

static struct mlx5e_rss *mlx5e_rss_init_copy(const struct mlx5e_rss *from)
{
 struct mlx5e_rss *rss;
 int err;

 rss = kvzalloc(sizeof(*rss), GFP_KERNEL);
 if (!rss)
  return ERR_PTR(-ENOMEM);

 err = mlx5e_rss_params_indir_init(&rss->indir, from->mdev, from->indir.actual_table_size,
       from->indir.max_table_size);
 if (err)
  goto err_free_rss;

 err = mlx5e_rss_copy(rss, from);
 if (err)
  goto err_free_indir;

 return rss;

err_free_indir:
 mlx5e_rss_params_indir_cleanup(&rss->indir);
err_free_rss:
 kvfree(rss);
 return ERR_PTR(err);
}

static void mlx5e_rss_params_init(struct mlx5e_rss *rss)
{
 enum mlx5_traffic_types tt;

 rss->hash.symmetric = true;
 rss->hash.hfunc = ETH_RSS_HASH_TOP;
 netdev_rss_key_fill(rss->hash.toeplitz_hash_key,
       sizeof(rss->hash.toeplitz_hash_key));
 for (tt = 0; tt < MLX5E_NUM_INDIR_TIRS; tt++)
  rss->rx_hash_fields[tt] =
   mlx5e_rss_get_default_tt_config(tt).rx_hash_fields;
}

static struct mlx5e_tir **rss_get_tirp(struct mlx5e_rss *rss, enum mlx5_traffic_types tt,
           bool inner)
{
 return inner ? &rss->inner_tir[tt] : &rss->tir[tt];
}

static struct mlx5e_tir *rss_get_tir(struct mlx5e_rss *rss, enum mlx5_traffic_types tt,
         bool inner)
{
 return *rss_get_tirp(rss, tt, inner);
}

static struct mlx5e_rss_params_traffic_type
mlx5e_rss_get_tt_config(struct mlx5e_rss *rss, enum mlx5_traffic_types tt)
{
 struct mlx5e_rss_params_traffic_type rss_tt;

 rss_tt = mlx5e_rss_get_default_tt_config(tt);
 rss_tt.rx_hash_fields = rss->rx_hash_fields[tt];
 return rss_tt;
}

static int mlx5e_rss_create_tir(struct mlx5e_rss *rss,
    enum mlx5_traffic_types tt,
    const struct mlx5e_packet_merge_param *init_pkt_merge_param,
    bool inner)
{
 struct mlx5e_rss_params_traffic_type rss_tt;
 struct mlx5e_tir_builder *builder;
 struct mlx5e_tir **tir_p;
 struct mlx5e_tir *tir;
 u32 rqtn;
 int err;

 if (inner && !rss->inner_ft_support) {
  mlx5e_rss_warn(rss->mdev,
          "Cannot create inner indirect TIR[%d], RSS inner FT is not supported.\n",
          tt);
  return -EINVAL;
 }

 tir_p = rss_get_tirp(rss, tt, inner);
 if (*tir_p)
  return -EINVAL;

 tir = kvzalloc(sizeof(*tir), GFP_KERNEL);
 if (!tir)
  return -ENOMEM;

 builder = mlx5e_tir_builder_alloc(false);
 if (!builder) {
  err = -ENOMEM;
  goto free_tir;
 }

 rqtn = mlx5e_rqt_get_rqtn(&rss->rqt);
 mlx5e_tir_builder_build_rqt(builder, rss->mdev->mlx5e_res.hw_objs.td.tdn,
        rqtn, rss->inner_ft_support);
 mlx5e_tir_builder_build_packet_merge(builder, init_pkt_merge_param);
 rss_tt = mlx5e_rss_get_tt_config(rss, tt);
 mlx5e_tir_builder_build_rss(builder, &rss->hash, &rss_tt, inner);

 err = mlx5e_tir_init(tir, builder, rss->mdev, true);
 mlx5e_tir_builder_free(builder);
 if (err) {
  mlx5e_rss_warn(rss->mdev, "Failed to create %sindirect TIR: err = %d, tt = %d\n",
          inner ? "inner " : "", err, tt);
  goto free_tir;
 }

 *tir_p = tir;
 return 0;

free_tir:
 kvfree(tir);
 return err;
}

static void mlx5e_rss_destroy_tir(struct mlx5e_rss *rss, enum mlx5_traffic_types tt,
      bool inner)
{
 struct mlx5e_tir **tir_p;
 struct mlx5e_tir *tir;

 tir_p = rss_get_tirp(rss, tt, inner);
 if (!*tir_p)
  return;

 tir = *tir_p;
 mlx5e_tir_destroy(tir);
 kvfree(tir);
 *tir_p = NULL;
}

static int mlx5e_rss_create_tirs(struct mlx5e_rss *rss,
     const struct mlx5e_packet_merge_param *init_pkt_merge_param,
     bool inner)
{
 enum mlx5_traffic_types tt, max_tt;
 int err;

 for (tt = 0; tt < MLX5E_NUM_INDIR_TIRS; tt++) {
  err = mlx5e_rss_create_tir(rss, tt, init_pkt_merge_param, inner);
  if (err)
   goto err_destroy_tirs;
 }

 return 0;

err_destroy_tirs:
 max_tt = tt;
 for (tt = 0; tt < max_tt; tt++)
  mlx5e_rss_destroy_tir(rss, tt, inner);
 return err;
}

static void mlx5e_rss_destroy_tirs(struct mlx5e_rss *rss, bool inner)
{
 enum mlx5_traffic_types tt;

 for (tt = 0; tt < MLX5E_NUM_INDIR_TIRS; tt++)
  mlx5e_rss_destroy_tir(rss, tt, inner);
}

static int mlx5e_rss_update_tir(struct mlx5e_rss *rss, enum mlx5_traffic_types tt,
    bool inner)
{
 struct mlx5e_rss_params_traffic_type rss_tt;
 struct mlx5e_tir_builder *builder;
 struct mlx5e_tir *tir;
 int err;

 tir = rss_get_tir(rss, tt, inner);
 if (!tir)
  return 0;

 builder = mlx5e_tir_builder_alloc(true);
 if (!builder)
  return -ENOMEM;

 rss_tt = mlx5e_rss_get_tt_config(rss, tt);

 mlx5e_tir_builder_build_rss(builder, &rss->hash, &rss_tt, inner);
 err = mlx5e_tir_modify(tir, builder);

 mlx5e_tir_builder_free(builder);
 return err;
}

static int mlx5e_rss_update_tirs(struct mlx5e_rss *rss)
{
 enum mlx5_traffic_types tt;
 int err, retval;

 retval = 0;

 for (tt = 0; tt < MLX5E_NUM_INDIR_TIRS; tt++) {
  err = mlx5e_rss_update_tir(rss, tt, false);
  if (err) {
   retval = retval ? : err;
   mlx5e_rss_warn(rss->mdev,
           "Failed to update RSS hash of indirect TIR for traffic type %d: err = %d\n",
           tt, err);
  }

  if (!rss->inner_ft_support)
   continue;

  err = mlx5e_rss_update_tir(rss, tt, true);
  if (err) {
   retval = retval ? : err;
   mlx5e_rss_warn(rss->mdev,
           "Failed to update RSS hash of inner indirect TIR for traffic type %d: err = %d\n",
           tt, err);
  }
 }
 return retval;
}

static int mlx5e_rss_init_no_tirs(struct mlx5e_rss *rss)
{
 mlx5e_rss_params_init(rss);
 refcount_set(&rss->refcnt, 1);

 return mlx5e_rqt_init_direct(&rss->rqt, rss->mdev, true,
         rss->drop_rqn, rss->indir.max_table_size);
}

struct mlx5e_rss *mlx5e_rss_init(struct mlx5_core_dev *mdev, bool inner_ft_support, u32 drop_rqn,
     const struct mlx5e_packet_merge_param *init_pkt_merge_param,
     enum mlx5e_rss_init_type type, unsigned int nch,
     unsigned int max_nch)
{
 struct mlx5e_rss *rss;
 int err;

 rss = kvzalloc(sizeof(*rss), GFP_KERNEL);
 if (!rss)
  return ERR_PTR(-ENOMEM);

 err = mlx5e_rss_params_indir_init(&rss->indir, mdev,
       mlx5e_rqt_size(mdev, nch),
       mlx5e_rqt_size(mdev, max_nch));
 if (err)
  goto err_free_rss;

 rss->mdev = mdev;
 rss->inner_ft_support = inner_ft_support;
 rss->drop_rqn = drop_rqn;

 err = mlx5e_rss_init_no_tirs(rss);
 if (err)
  goto err_free_indir;

 if (type == MLX5E_RSS_INIT_NO_TIRS)
  goto out;

 err = mlx5e_rss_create_tirs(rss, init_pkt_merge_param, false);
 if (err)
  goto err_destroy_rqt;

 if (inner_ft_support) {
  err = mlx5e_rss_create_tirs(rss, init_pkt_merge_param, true);
  if (err)
   goto err_destroy_tirs;
 }

out:
 return rss;

err_destroy_tirs:
 mlx5e_rss_destroy_tirs(rss, false);
err_destroy_rqt:
 mlx5e_rqt_destroy(&rss->rqt);
err_free_indir:
 mlx5e_rss_params_indir_cleanup(&rss->indir);
err_free_rss:
 kvfree(rss);
 return ERR_PTR(err);
}

int mlx5e_rss_cleanup(struct mlx5e_rss *rss)
{
 if (!refcount_dec_if_one(&rss->refcnt))
  return -EBUSY;

 mlx5e_rss_destroy_tirs(rss, false);

 if (rss->inner_ft_support)
  mlx5e_rss_destroy_tirs(rss, true);

 mlx5e_rqt_destroy(&rss->rqt);
 mlx5e_rss_params_indir_cleanup(&rss->indir);
 kvfree(rss);

 return 0;
}

void mlx5e_rss_refcnt_inc(struct mlx5e_rss *rss)
{
 refcount_inc(&rss->refcnt);
}

void mlx5e_rss_refcnt_dec(struct mlx5e_rss *rss)
{
 refcount_dec(&rss->refcnt);
}

unsigned int mlx5e_rss_refcnt_read(struct mlx5e_rss *rss)
{
 return refcount_read(&rss->refcnt);
}

u32 mlx5e_rss_get_tirn(struct mlx5e_rss *rss, enum mlx5_traffic_types tt,
         bool inner)
{
 struct mlx5e_tir *tir;

 WARN_ON(inner && !rss->inner_ft_support);
 tir = rss_get_tir(rss, tt, inner);
 WARN_ON(!tir);

 return mlx5e_tir_get_tirn(tir);
}

u32 mlx5e_rss_get_rqtn(struct mlx5e_rss *rss)
{
 return mlx5e_rqt_get_rqtn(&rss->rqt);
}

bool mlx5e_rss_valid_tir(struct mlx5e_rss *rss, enum mlx5_traffic_types tt, bool inner)
{
 return !!rss_get_tir(rss, tt, inner);
}

/* Fill the "tirn" output parameter.
 * Create the requested TIR if it's its first usage.
 */

int mlx5e_rss_obtain_tirn(struct mlx5e_rss *rss,
     enum mlx5_traffic_types tt,
     const struct mlx5e_packet_merge_param *init_pkt_merge_param,
     bool inner, u32 *tirn)
{
 struct mlx5e_tir *tir;

 tir = rss_get_tir(rss, tt, inner);
 if (!tir) { /* TIR doesn't exist, create one */
  int err;

  err = mlx5e_rss_create_tir(rss, tt, init_pkt_merge_param, inner);
  if (err)
   return err;
  tir = rss_get_tir(rss, tt, inner);
 }

 *tirn = mlx5e_tir_get_tirn(tir);
 return 0;
}

static int mlx5e_rss_apply(struct mlx5e_rss *rss, u32 *rqns, u32 *vhca_ids, unsigned int num_rqns)
{
 int err;

 err = mlx5e_rqt_redirect_indir(&rss->rqt, rqns, vhca_ids, num_rqns, rss->hash.hfunc,
           &rss->indir);
 if (err)
  mlx5e_rss_warn(rss->mdev, "Failed to redirect RQT %#x to channels: err = %d\n",
          mlx5e_rqt_get_rqtn(&rss->rqt), err);
 return err;
}

void mlx5e_rss_enable(struct mlx5e_rss *rss, u32 *rqns, u32 *vhca_ids, unsigned int num_rqns)
{
 rss->enabled = true;
 mlx5e_rss_apply(rss, rqns, vhca_ids, num_rqns);
}

void mlx5e_rss_disable(struct mlx5e_rss *rss)
{
 int err;

 rss->enabled = false;
 err = mlx5e_rqt_redirect_direct(&rss->rqt, rss->drop_rqn, NULL);
 if (err)
  mlx5e_rss_warn(rss->mdev, "Failed to redirect RQT %#x to drop RQ %#x: err = %d\n",
          mlx5e_rqt_get_rqtn(&rss->rqt), rss->drop_rqn, err);
}

int mlx5e_rss_packet_merge_set_param(struct mlx5e_rss *rss,
         struct mlx5e_packet_merge_param *pkt_merge_param)
{
 struct mlx5e_tir_builder *builder;
 enum mlx5_traffic_types tt;
 int err, final_err;

 builder = mlx5e_tir_builder_alloc(true);
 if (!builder)
  return -ENOMEM;

 mlx5e_tir_builder_build_packet_merge(builder, pkt_merge_param);

 final_err = 0;

 for (tt = 0; tt < MLX5E_NUM_INDIR_TIRS; tt++) {
  struct mlx5e_tir *tir;

  tir = rss_get_tir(rss, tt, false);
  if (!tir)
   goto inner_tir;
  err = mlx5e_tir_modify(tir, builder);
  if (err) {
   mlx5e_rss_warn(rss->mdev, "Failed to update packet merge state of indirect TIR %#x for traffic type %d: err = %d\n",
           mlx5e_tir_get_tirn(tir), tt, err);
   if (!final_err)
    final_err = err;
  }

inner_tir:
  if (!rss->inner_ft_support)
   continue;

  tir = rss_get_tir(rss, tt, true);
  if (!tir)
   continue;
  err = mlx5e_tir_modify(tir, builder);
  if (err) {
   mlx5e_rss_warn(rss->mdev, "Failed to update packet merge state of inner indirect TIR %#x for traffic type %d: err = %d\n",
           mlx5e_tir_get_tirn(tir), tt, err);
   if (!final_err)
    final_err = err;
  }
 }

 mlx5e_tir_builder_free(builder);
 return final_err;
}

void mlx5e_rss_get_rxfh(struct mlx5e_rss *rss, u32 *indir, u8 *key, u8 *hfunc,
   bool *symmetric)
{
 if (indir)
  memcpy(indir, rss->indir.table,
         rss->indir.actual_table_size * sizeof(*rss->indir.table));

 if (key)
  memcpy(key, rss->hash.toeplitz_hash_key,
         sizeof(rss->hash.toeplitz_hash_key));

 if (hfunc)
  *hfunc = rss->hash.hfunc;

 if (symmetric)
  *symmetric = rss->hash.symmetric;
}

int mlx5e_rss_set_rxfh(struct mlx5e_rss *rss, const u32 *indir,
         const u8 *key, const u8 *hfunc, const bool *symmetric,
         u32 *rqns, u32 *vhca_ids, unsigned int num_rqns)
{
 bool changed_indir = false;
 bool changed_hash = false;
 struct mlx5e_rss *old_rss;
 int err = 0;

 old_rss = mlx5e_rss_init_copy(rss);
 if (IS_ERR(old_rss))
  return PTR_ERR(old_rss);

 if (hfunc && *hfunc != rss->hash.hfunc) {
  switch (*hfunc) {
  case ETH_RSS_HASH_XOR:
  case ETH_RSS_HASH_TOP:
   break;
  default:
   err = -EINVAL;
   goto out;
  }
  changed_hash = true;
  changed_indir = true;
  rss->hash.hfunc = *hfunc;
 }

 if (key) {
  if (rss->hash.hfunc == ETH_RSS_HASH_TOP)
   changed_hash = true;
  memcpy(rss->hash.toeplitz_hash_key, key,
         sizeof(rss->hash.toeplitz_hash_key));
 }

 if (indir) {
  changed_indir = true;

  memcpy(rss->indir.table, indir,
         rss->indir.actual_table_size * sizeof(*rss->indir.table));
 }

 if (symmetric) {
  rss->hash.symmetric = *symmetric;
  changed_hash = true;
 }

 if (changed_indir && rss->enabled) {
  err = mlx5e_rss_apply(rss, rqns, vhca_ids, num_rqns);
  if (err) {
   mlx5e_rss_copy(rss, old_rss);
   goto out;
  }
 }

 if (changed_hash)
  mlx5e_rss_update_tirs(rss);

out:
 mlx5e_rss_params_indir_cleanup(&old_rss->indir);
 kvfree(old_rss);

 return err;
}

struct mlx5e_rss_params_hash mlx5e_rss_get_hash(struct mlx5e_rss *rss)
{
 return rss->hash;
}

u8 mlx5e_rss_get_hash_fields(struct mlx5e_rss *rss, enum mlx5_traffic_types tt)
{
 return rss->rx_hash_fields[tt];
}

int mlx5e_rss_set_hash_fields(struct mlx5e_rss *rss, enum mlx5_traffic_types tt,
         u8 rx_hash_fields)
{
 u8 old_rx_hash_fields;
 int err;

 old_rx_hash_fields = rss->rx_hash_fields[tt];

 if (old_rx_hash_fields == rx_hash_fields)
  return 0;

 rss->rx_hash_fields[tt] = rx_hash_fields;

 err = mlx5e_rss_update_tir(rss, tt, false);
 if (err) {
  rss->rx_hash_fields[tt] = old_rx_hash_fields;
  mlx5e_rss_warn(rss->mdev,
          "Failed to update RSS hash fields of indirect TIR for traffic type %d: err = %d\n",
          tt, err);
  return err;
 }

 if (!(rss->inner_ft_support))
  return 0;

 err = mlx5e_rss_update_tir(rss, tt, true);
 if (err) {
  /* Partial update happened. Try to revert - it may fail too, but
 * there is nothing more we can do.
 */

  rss->rx_hash_fields[tt] = old_rx_hash_fields;
  mlx5e_rss_warn(rss->mdev,
          "Failed to update RSS hash fields of inner indirect TIR for traffic type %d: err = %d\n",
          tt, err);
  if (mlx5e_rss_update_tir(rss, tt, false))
   mlx5e_rss_warn(rss->mdev,
           "Partial update of RSS hash fields happened: failed to revert indirect TIR for traffic type %d to the old values\n",
           tt);
 }

 return err;
}

void mlx5e_rss_set_indir_uniform(struct mlx5e_rss *rss, unsigned int nch)
{
 mlx5e_rss_params_indir_init_uniform(&rss->indir, nch);
}

Messung V0.5 in Prozent
C=95 H=91 G=92

[0.14QuellennavigatorsProjekt 2026-06-07]