[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <aKSYMxObH1n9luEs@lizhi-Precision-Tower-5810>
Date: Tue, 19 Aug 2025 11:28:51 -0400
From: Frank Li <Frank.li@....com>
To: Wei Fang <wei.fang@....com>
Cc: robh@...nel.org, krzk+dt@...nel.org, conor+dt@...nel.org,
richardcochran@...il.com, claudiu.manoil@....com,
vladimir.oltean@....com, xiaoning.wang@....com,
andrew+netdev@...n.ch, davem@...emloft.net, edumazet@...gle.com,
kuba@...nel.org, pabeni@...hat.com, vadim.fedorenko@...ux.dev,
shawnguo@...nel.org, s.hauer@...gutronix.de, festevam@...il.com,
fushi.peng@....com, devicetree@...r.kernel.org,
netdev@...r.kernel.org, linux-kernel@...r.kernel.org,
imx@...ts.linux.dev, kernel@...gutronix.de
Subject: Re: [PATCH v4 net-next 06/15] ptp: netc: add periodic pulse output
support
On Tue, Aug 19, 2025 at 08:36:11PM +0800, Wei Fang wrote:
> NETC Timer has three pulse channels, all of which support periodic pulse
> output. Bind the channel to a ALARM register and then sets a future time
> into the ALARM register. When the current time is greater than the ALARM
> value, the FIPER register will be triggered to count down, and when the
> count reaches 0, the pulse will be triggered. The PPS signal is also
> implemented in this way.
>
> i.MX95 only has ALARM1 can be used as an indication to the FIPER start
> down counting, but i.MX943 has ALARM1 and ALARM2 can be used. Therefore,
> only one channel can work for i.MX95, two channels for i.MX943 as most.
>
> In addition, change the PPS channel to be dynamically selected from fixed
> number (0) because add PTP_CLK_REQ_PEROUT support.
>
> Signed-off-by: Wei Fang <wei.fang@....com>
Reviewed-by: Frank Li <Frank.Li@....com>
>
> ---
> v2: no changes
> v3 changes:
> 1. Improve the commit message
> 2. Add revision to struct netc_timer
> 3. Use priv->tmr_emask to instead of reading TMR_EMASK register
> 4. Add pps_channel to struct netc_timer and NETC_TMR_INVALID_CHANNEL
> 5. Add some helper functions: netc_timer_enable/disable_periodic_pulse(),
> and netc_timer_select_pps_channel()
> 6. Dynamically select PPS channel instead of fixed to channel 0.
> v4:
> 1. Simplify the commit message
> 2. Fix dereference unassigned pointer "ps" in netc_timer_enable_pps().
> ---
> drivers/ptp/ptp_netc.c | 356 +++++++++++++++++++++++++++++++++++------
> 1 file changed, 306 insertions(+), 50 deletions(-)
>
> diff --git a/drivers/ptp/ptp_netc.c b/drivers/ptp/ptp_netc.c
> index ded2509700b5..da9603c65dda 100644
> --- a/drivers/ptp/ptp_netc.c
> +++ b/drivers/ptp/ptp_netc.c
> @@ -52,12 +52,18 @@
> #define NETC_TMR_CUR_TIME_H 0x00f4
>
> #define NETC_TMR_REGS_BAR 0
> +#define NETC_GLOBAL_OFFSET 0x10000
> +#define NETC_GLOBAL_IPBRR0 0xbf8
> +#define IPBRR0_IP_REV GENMASK(15, 0)
> +#define NETC_REV_4_1 0x0401
>
> #define NETC_TMR_FIPER_NUM 3
> +#define NETC_TMR_INVALID_CHANNEL NETC_TMR_FIPER_NUM
> #define NETC_TMR_DEFAULT_PRSC 2
> #define NETC_TMR_DEFAULT_ALARM GENMASK_ULL(63, 0)
> #define NETC_TMR_DEFAULT_FIPER GENMASK(31, 0)
> #define NETC_TMR_FIPER_MAX_PW GENMASK(4, 0)
> +#define NETC_TMR_ALARM_NUM 2
>
> /* 1588 timer reference clock source select */
> #define NETC_TMR_CCM_TIMER1 0 /* enet_timer1_clk_root, from CCM */
> @@ -66,6 +72,19 @@
>
> #define NETC_TMR_SYSCLK_333M 333333333U
>
> +enum netc_pp_type {
> + NETC_PP_PPS = 1,
> + NETC_PP_PEROUT,
> +};
> +
> +struct netc_pp {
> + enum netc_pp_type type;
> + bool enabled;
> + int alarm_id;
> + u32 period; /* pulse period, ns */
> + u64 stime; /* start time, ns */
> +};
> +
> struct netc_timer {
> void __iomem *base;
> struct pci_dev *pdev;
> @@ -80,8 +99,12 @@ struct netc_timer {
> u64 period;
>
> int irq;
> + int revision;
> u32 tmr_emask;
> - bool pps_enabled;
> + u8 pps_channel;
> + u8 fs_alarm_num;
> + u8 fs_alarm_bitmap;
> + struct netc_pp pp[NETC_TMR_FIPER_NUM]; /* periodic pulse */
> };
>
> #define netc_timer_rd(p, o) netc_read((p)->base + (o))
> @@ -190,6 +213,7 @@ static u32 netc_timer_calculate_fiper_pw(struct netc_timer *priv,
> static void netc_timer_set_pps_alarm(struct netc_timer *priv, int channel,
> u32 integral_period)
> {
> + struct netc_pp *pp = &priv->pp[channel];
> u64 alarm;
>
> /* Get the alarm value */
> @@ -197,7 +221,116 @@ static void netc_timer_set_pps_alarm(struct netc_timer *priv, int channel,
> alarm = roundup_u64(alarm, NSEC_PER_SEC);
> alarm = roundup_u64(alarm, integral_period);
>
> - netc_timer_alarm_write(priv, alarm, 0);
> + netc_timer_alarm_write(priv, alarm, pp->alarm_id);
> +}
> +
> +static void netc_timer_set_perout_alarm(struct netc_timer *priv, int channel,
> + u32 integral_period)
> +{
> + u64 cur_time = netc_timer_cur_time_read(priv);
> + struct netc_pp *pp = &priv->pp[channel];
> + u64 alarm, delta, min_time;
> + u32 period = pp->period;
> + u64 stime = pp->stime;
> +
> + min_time = cur_time + NSEC_PER_MSEC + period;
> + if (stime < min_time) {
> + delta = min_time - stime;
> + stime += roundup_u64(delta, period);
> + }
> +
> + alarm = roundup_u64(stime - period, integral_period);
> + netc_timer_alarm_write(priv, alarm, pp->alarm_id);
> +}
> +
> +static int netc_timer_get_alarm_id(struct netc_timer *priv)
> +{
> + int i;
> +
> + for (i = 0; i < priv->fs_alarm_num; i++) {
> + if (!(priv->fs_alarm_bitmap & BIT(i))) {
> + priv->fs_alarm_bitmap |= BIT(i);
> + break;
> + }
> + }
> +
> + return i;
> +}
> +
> +static u64 netc_timer_get_gclk_period(struct netc_timer *priv)
> +{
> + /* TMR_GCLK_freq = (clk_freq / oclk_prsc) Hz.
> + * TMR_GCLK_period = NSEC_PER_SEC / TMR_GCLK_freq.
> + * TMR_GCLK_period = (NSEC_PER_SEC * oclk_prsc) / clk_freq
> + */
> +
> + return div_u64(mul_u32_u32(NSEC_PER_SEC, priv->oclk_prsc),
> + priv->clk_freq);
> +}
> +
> +static void netc_timer_enable_periodic_pulse(struct netc_timer *priv,
> + u8 channel)
> +{
> + u32 fiper_pw, fiper, fiper_ctrl, integral_period;
> + struct netc_pp *pp = &priv->pp[channel];
> + int alarm_id = pp->alarm_id;
> +
> + integral_period = netc_timer_get_integral_period(priv);
> + /* Set to desired FIPER interval in ns - TCLK_PERIOD */
> + fiper = pp->period - integral_period;
> + fiper_pw = netc_timer_calculate_fiper_pw(priv, fiper);
> +
> + fiper_ctrl = netc_timer_rd(priv, NETC_TMR_FIPER_CTRL);
> + fiper_ctrl &= ~(FIPER_CTRL_DIS(channel) | FIPER_CTRL_PW(channel) |
> + FIPER_CTRL_FS_ALARM(channel));
> + fiper_ctrl |= FIPER_CTRL_SET_PW(channel, fiper_pw);
> + fiper_ctrl |= alarm_id ? FIPER_CTRL_FS_ALARM(channel) : 0;
> +
> + priv->tmr_emask |= TMR_TEVNET_PPEN(channel) |
> + TMR_TEVENT_ALMEN(alarm_id);
> +
> + if (pp->type == NETC_PP_PPS)
> + netc_timer_set_pps_alarm(priv, channel, integral_period);
> + else
> + netc_timer_set_perout_alarm(priv, channel, integral_period);
> +
> + netc_timer_wr(priv, NETC_TMR_TEMASK, priv->tmr_emask);
> + netc_timer_wr(priv, NETC_TMR_FIPER(channel), fiper);
> + netc_timer_wr(priv, NETC_TMR_FIPER_CTRL, fiper_ctrl);
> +}
> +
> +static void netc_timer_disable_periodic_pulse(struct netc_timer *priv,
> + u8 channel)
> +{
> + struct netc_pp *pp = &priv->pp[channel];
> + int alarm_id = pp->alarm_id;
> + u32 fiper_ctrl;
> +
> + if (!pp->enabled)
> + return;
> +
> + priv->tmr_emask &= ~(TMR_TEVNET_PPEN(channel) |
> + TMR_TEVENT_ALMEN(alarm_id));
> +
> + fiper_ctrl = netc_timer_rd(priv, NETC_TMR_FIPER_CTRL);
> + fiper_ctrl |= FIPER_CTRL_DIS(channel);
> +
> + netc_timer_alarm_write(priv, NETC_TMR_DEFAULT_ALARM, alarm_id);
> + netc_timer_wr(priv, NETC_TMR_TEMASK, priv->tmr_emask);
> + netc_timer_wr(priv, NETC_TMR_FIPER(channel), NETC_TMR_DEFAULT_FIPER);
> + netc_timer_wr(priv, NETC_TMR_FIPER_CTRL, fiper_ctrl);
> +}
> +
> +static u8 netc_timer_select_pps_channel(struct netc_timer *priv)
> +{
> + int i;
> +
> + for (i = 0; i < NETC_TMR_FIPER_NUM; i++) {
> + if (!priv->pp[i].enabled)
> + return i;
> + }
> +
> + return NETC_TMR_INVALID_CHANNEL;
> }
>
> /* Note that users should not use this API to output PPS signal on
> @@ -208,77 +341,178 @@ static void netc_timer_set_pps_alarm(struct netc_timer *priv, int channel,
> static int netc_timer_enable_pps(struct netc_timer *priv,
> struct ptp_clock_request *rq, int on)
> {
> - u32 fiper, fiper_ctrl;
> + struct device *dev = &priv->pdev->dev;
> unsigned long flags;
> + struct netc_pp *pp;
> + int err = 0;
>
> spin_lock_irqsave(&priv->lock, flags);
>
> - fiper_ctrl = netc_timer_rd(priv, NETC_TMR_FIPER_CTRL);
> -
> if (on) {
> - u32 integral_period, fiper_pw;
> + int alarm_id;
> + u8 channel;
> +
> + if (priv->pps_channel < NETC_TMR_FIPER_NUM) {
> + channel = priv->pps_channel;
> + } else {
> + channel = netc_timer_select_pps_channel(priv);
> + if (channel == NETC_TMR_INVALID_CHANNEL) {
> + dev_err(dev, "No available FIPERs\n");
> + err = -EBUSY;
> + goto unlock_spinlock;
> + }
> + }
>
> - if (priv->pps_enabled)
> + pp = &priv->pp[channel];
> + if (pp->enabled)
> goto unlock_spinlock;
>
> - integral_period = netc_timer_get_integral_period(priv);
> - fiper = NSEC_PER_SEC - integral_period;
> - fiper_pw = netc_timer_calculate_fiper_pw(priv, fiper);
> - fiper_ctrl &= ~(FIPER_CTRL_DIS(0) | FIPER_CTRL_PW(0) |
> - FIPER_CTRL_FS_ALARM(0));
> - fiper_ctrl |= FIPER_CTRL_SET_PW(0, fiper_pw);
> - priv->tmr_emask |= TMR_TEVNET_PPEN(0) | TMR_TEVENT_ALMEN(0);
> - priv->pps_enabled = true;
> - netc_timer_set_pps_alarm(priv, 0, integral_period);
> + alarm_id = netc_timer_get_alarm_id(priv);
> + if (alarm_id == priv->fs_alarm_num) {
> + dev_err(dev, "No available ALARMs\n");
> + err = -EBUSY;
> + goto unlock_spinlock;
> + }
> +
> + pp->enabled = true;
> + pp->type = NETC_PP_PPS;
> + pp->alarm_id = alarm_id;
> + pp->period = NSEC_PER_SEC;
> + priv->pps_channel = channel;
> +
> + netc_timer_enable_periodic_pulse(priv, channel);
> } else {
> - if (!priv->pps_enabled)
> + /* pps_channel is invalid if PPS is not enabled, so no
> + * processing is needed.
> + */
> + if (priv->pps_channel >= NETC_TMR_FIPER_NUM)
> goto unlock_spinlock;
>
> - fiper = NETC_TMR_DEFAULT_FIPER;
> - priv->tmr_emask &= ~(TMR_TEVNET_PPEN(0) |
> - TMR_TEVENT_ALMEN(0));
> - fiper_ctrl |= FIPER_CTRL_DIS(0);
> - priv->pps_enabled = false;
> - netc_timer_alarm_write(priv, NETC_TMR_DEFAULT_ALARM, 0);
> + netc_timer_disable_periodic_pulse(priv, priv->pps_channel);
> + pp = &priv->pp[priv->pps_channel];
> + priv->fs_alarm_bitmap &= ~BIT(pp->alarm_id);
> + memset(pp, 0, sizeof(*pp));
> + priv->pps_channel = NETC_TMR_INVALID_CHANNEL;
> }
>
> - netc_timer_wr(priv, NETC_TMR_TEMASK, priv->tmr_emask);
> - netc_timer_wr(priv, NETC_TMR_FIPER(0), fiper);
> - netc_timer_wr(priv, NETC_TMR_FIPER_CTRL, fiper_ctrl);
> +unlock_spinlock:
> + spin_unlock_irqrestore(&priv->lock, flags);
> +
> + return err;
> +}
> +
> +static int net_timer_enable_perout(struct netc_timer *priv,
> + struct ptp_clock_request *rq, int on)
> +{
> + struct device *dev = &priv->pdev->dev;
> + u32 channel = rq->perout.index;
> + unsigned long flags;
> + struct netc_pp *pp;
> + int err = 0;
> +
> + spin_lock_irqsave(&priv->lock, flags);
> +
> + pp = &priv->pp[channel];
> + if (pp->type == NETC_PP_PPS) {
> + dev_err(dev, "FIPER%u is being used for PPS\n", channel);
> + err = -EBUSY;
> + goto unlock_spinlock;
> + }
> +
> + if (on) {
> + u64 period_ns, gclk_period, max_period, min_period;
> + struct timespec64 period, stime;
> + u32 integral_period;
> + int alarm_id;
> +
> + period.tv_sec = rq->perout.period.sec;
> + period.tv_nsec = rq->perout.period.nsec;
> + period_ns = timespec64_to_ns(&period);
> +
> + integral_period = netc_timer_get_integral_period(priv);
> + max_period = (u64)NETC_TMR_DEFAULT_FIPER + integral_period;
> + gclk_period = netc_timer_get_gclk_period(priv);
> + min_period = gclk_period * 4 + integral_period;
> + if (period_ns > max_period || period_ns < min_period) {
> + dev_err(dev, "The period range is %llu ~ %llu\n",
> + min_period, max_period);
> + err = -EINVAL;
> + goto unlock_spinlock;
> + }
> +
> + if (pp->enabled) {
> + alarm_id = pp->alarm_id;
> + } else {
> + alarm_id = netc_timer_get_alarm_id(priv);
> + if (alarm_id == priv->fs_alarm_num) {
> + dev_err(dev, "No available ALARMs\n");
> + err = -EBUSY;
> + goto unlock_spinlock;
> + }
> +
> + pp->type = NETC_PP_PEROUT;
> + pp->enabled = true;
> + pp->alarm_id = alarm_id;
> + }
> +
> + stime.tv_sec = rq->perout.start.sec;
> + stime.tv_nsec = rq->perout.start.nsec;
> + pp->stime = timespec64_to_ns(&stime);
> + pp->period = period_ns;
> +
> + netc_timer_enable_periodic_pulse(priv, channel);
> + } else {
> + netc_timer_disable_periodic_pulse(priv, channel);
> + priv->fs_alarm_bitmap &= ~BIT(pp->alarm_id);
> + memset(pp, 0, sizeof(*pp));
> + }
>
> unlock_spinlock:
> spin_unlock_irqrestore(&priv->lock, flags);
>
> - return 0;
> + return err;
> }
>
> -static void netc_timer_disable_pps_fiper(struct netc_timer *priv)
> +static void netc_timer_disable_fiper(struct netc_timer *priv)
> {
> - u32 fiper_ctrl;
> + u32 fiper_ctrl = netc_timer_rd(priv, NETC_TMR_FIPER_CTRL);
> + int i;
>
> - if (!priv->pps_enabled)
> - return;
> + for (i = 0; i < NETC_TMR_FIPER_NUM; i++) {
> + if (!priv->pp[i].enabled)
> + continue;
> +
> + fiper_ctrl |= FIPER_CTRL_DIS(i);
> + netc_timer_wr(priv, NETC_TMR_FIPER(i), NETC_TMR_DEFAULT_FIPER);
> + }
>
> - fiper_ctrl = netc_timer_rd(priv, NETC_TMR_FIPER_CTRL);
> - fiper_ctrl |= FIPER_CTRL_DIS(0);
> - netc_timer_wr(priv, NETC_TMR_FIPER(0), NETC_TMR_DEFAULT_FIPER);
> netc_timer_wr(priv, NETC_TMR_FIPER_CTRL, fiper_ctrl);
> }
>
> -static void netc_timer_enable_pps_fiper(struct netc_timer *priv)
> +static void netc_timer_enable_fiper(struct netc_timer *priv)
> {
> - u32 fiper_ctrl, integral_period, fiper;
> + u32 integral_period = netc_timer_get_integral_period(priv);
> + u32 fiper_ctrl = netc_timer_rd(priv, NETC_TMR_FIPER_CTRL);
> + int i;
>
> - if (!priv->pps_enabled)
> - return;
> + for (i = 0; i < NETC_TMR_FIPER_NUM; i++) {
> + struct netc_pp *pp = &priv->pp[i];
> + u32 fiper;
>
> - integral_period = netc_timer_get_integral_period(priv);
> - fiper_ctrl = netc_timer_rd(priv, NETC_TMR_FIPER_CTRL);
> - fiper_ctrl &= ~FIPER_CTRL_DIS(0);
> - fiper = NSEC_PER_SEC - integral_period;
> + if (!pp->enabled)
> + continue;
> +
> + fiper_ctrl &= ~FIPER_CTRL_DIS(i);
> +
> + if (pp->type == NETC_PP_PPS)
> + netc_timer_set_pps_alarm(priv, i, integral_period);
> + else if (pp->type == NETC_PP_PEROUT)
> + netc_timer_set_perout_alarm(priv, i, integral_period);
> +
> + fiper = pp->period - integral_period;
> + netc_timer_wr(priv, NETC_TMR_FIPER(i), fiper);
> + }
>
> - netc_timer_set_pps_alarm(priv, 0, integral_period);
> - netc_timer_wr(priv, NETC_TMR_FIPER(0), fiper);
> netc_timer_wr(priv, NETC_TMR_FIPER_CTRL, fiper_ctrl);
> }
>
> @@ -290,6 +524,8 @@ static int netc_timer_enable(struct ptp_clock_info *ptp,
> switch (rq->type) {
> case PTP_CLK_REQ_PPS:
> return netc_timer_enable_pps(priv, rq, on);
> + case PTP_CLK_REQ_PEROUT:
> + return net_timer_enable_perout(priv, rq, on);
> default:
> return -EOPNOTSUPP;
> }
> @@ -308,9 +544,9 @@ static void netc_timer_adjust_period(struct netc_timer *priv, u64 period)
> tmr_ctrl = u32_replace_bits(old_tmr_ctrl, integral_period,
> TMR_CTRL_TCLK_PERIOD);
> if (tmr_ctrl != old_tmr_ctrl) {
> - netc_timer_disable_pps_fiper(priv);
> + netc_timer_disable_fiper(priv);
> netc_timer_wr(priv, NETC_TMR_CTRL, tmr_ctrl);
> - netc_timer_enable_pps_fiper(priv);
> + netc_timer_enable_fiper(priv);
> }
>
> netc_timer_wr(priv, NETC_TMR_ADD, fractional_period);
> @@ -337,7 +573,7 @@ static int netc_timer_adjtime(struct ptp_clock_info *ptp, s64 delta)
>
> spin_lock_irqsave(&priv->lock, flags);
>
> - netc_timer_disable_pps_fiper(priv);
> + netc_timer_disable_fiper(priv);
>
> /* Adjusting TMROFF instead of TMR_CNT is that the timer
> * counter keeps increasing during reading and writing
> @@ -347,7 +583,7 @@ static int netc_timer_adjtime(struct ptp_clock_info *ptp, s64 delta)
> tmr_off += delta;
> netc_timer_offset_write(priv, tmr_off);
>
> - netc_timer_enable_pps_fiper(priv);
> + netc_timer_enable_fiper(priv);
>
> spin_unlock_irqrestore(&priv->lock, flags);
>
> @@ -384,10 +620,10 @@ static int netc_timer_settime64(struct ptp_clock_info *ptp,
>
> spin_lock_irqsave(&priv->lock, flags);
>
> - netc_timer_disable_pps_fiper(priv);
> + netc_timer_disable_fiper(priv);
> netc_timer_offset_write(priv, 0);
> netc_timer_cnt_write(priv, ns);
> - netc_timer_enable_pps_fiper(priv);
> + netc_timer_enable_fiper(priv);
>
> spin_unlock_irqrestore(&priv->lock, flags);
>
> @@ -401,6 +637,7 @@ static const struct ptp_clock_info netc_timer_ptp_caps = {
> .n_pins = 0,
> .n_alarm = 2,
> .pps = 1,
> + .n_per_out = 3,
> .adjfine = netc_timer_adjfine,
> .adjtime = netc_timer_adjtime,
> .gettimex64 = netc_timer_gettimex64,
> @@ -558,6 +795,9 @@ static irqreturn_t netc_timer_isr(int irq, void *data)
> if (tmr_event & TMR_TEVENT_ALMEN(0))
> netc_timer_alarm_write(priv, NETC_TMR_DEFAULT_ALARM, 0);
>
> + if (tmr_event & TMR_TEVENT_ALMEN(1))
> + netc_timer_alarm_write(priv, NETC_TMR_DEFAULT_ALARM, 1);
> +
> if (tmr_event & TMR_TEVENT_PPEN_ALL) {
> event.type = PTP_CLOCK_PPS;
> ptp_clock_event(priv->clock, &event);
> @@ -602,6 +842,15 @@ static void netc_timer_free_msix_irq(struct netc_timer *priv)
> pci_free_irq_vectors(pdev);
> }
>
> +static int netc_timer_get_global_ip_rev(struct netc_timer *priv)
> +{
> + u32 val;
> +
> + val = netc_timer_rd(priv, NETC_GLOBAL_OFFSET + NETC_GLOBAL_IPBRR0);
> +
> + return val & IPBRR0_IP_REV;
> +}
> +
> static int netc_timer_probe(struct pci_dev *pdev,
> const struct pci_device_id *id)
> {
> @@ -614,12 +863,19 @@ static int netc_timer_probe(struct pci_dev *pdev,
> return err;
>
> priv = pci_get_drvdata(pdev);
> + priv->revision = netc_timer_get_global_ip_rev(priv);
> + if (priv->revision == NETC_REV_4_1)
> + priv->fs_alarm_num = 1;
> + else
> + priv->fs_alarm_num = NETC_TMR_ALARM_NUM;
> +
> err = netc_timer_parse_dt(priv);
> if (err)
> goto timer_pci_remove;
>
> priv->caps = netc_timer_ptp_caps;
> priv->oclk_prsc = NETC_TMR_DEFAULT_PRSC;
> + priv->pps_channel = NETC_TMR_INVALID_CHANNEL;
> spin_lock_init(&priv->lock);
>
> err = netc_timer_init_msix_irq(priv);
> --
> 2.34.1
>
Powered by blists - more mailing lists