Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id AAF43C433F5 for ; Thu, 18 Nov 2021 03:22:33 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 8AEC26142A for ; Thu, 18 Nov 2021 03:22:33 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S242588AbhKRDZb (ORCPT ); Wed, 17 Nov 2021 22:25:31 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:34938 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S241204AbhKRDZY (ORCPT ); Wed, 17 Nov 2021 22:25:24 -0500 Received: from mail-pg1-x52e.google.com (mail-pg1-x52e.google.com [IPv6:2607:f8b0:4864:20::52e]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id EAAAAC061570 for ; Wed, 17 Nov 2021 19:22:24 -0800 (PST) Received: by mail-pg1-x52e.google.com with SMTP id 28so4052367pgq.8 for ; Wed, 17 Nov 2021 19:22:24 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=broadcom.com; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version; bh=8NXt+59mg8Ib3ZGdHhGPHWxCalz03HIOLOSspYHn70E=; b=fe3AzcNkvzBKnZPUg9meK7RRktlqGP1vnnNtGfpwRGx2rc7Ij54GZzE3o6TLRRbXF7 AURGRNJce6rpf/TgmFrc1RESu39z9TPhKhWzfEOeJJE1bCVitLmS/GZSFbpfUttUP02M iyFt1C3dtdSI1wdB6n1tHiVyRb9QaGE7Kb4FU= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version; bh=8NXt+59mg8Ib3ZGdHhGPHWxCalz03HIOLOSspYHn70E=; b=jA2JtfEbJ5BtkKcqAXV3fDy4skgVFJp9HkKRg/H9OviKuyry294CTVDkyiBjkIMEmS nwavmOQZ4oWuKd0keIeiI5ljY0yXKhgx5yPgEN4MhU0GrLk42eHXm2N5dmtxCBKtjeuf LNxFj1zBZT7VU7kL3whuKJN9euwCMorSdD6Fyt07R3ViPFLR1mrQQS3rAfRicNMP4MLA MLnQ/GNYJGh7JdIaNR0aGeaNpc+khzFOe6sWw3SgsMEI9YGZV3Zr4sd4ofN/VJFSSnn/ 2CnF9aSXUDEi8t0tr/7xpqY7t3tU/LBFlgPpvo5+uHRSrfWZBxq+hJDB1F6sqvOcXUUm jTMg== X-Gm-Message-State: AOAM532Js7pDRo7sWwAwPK1pGcjcyu9NTPnh1XF3dUJSI6pjdNPoqZ9S fWgTbcBN6fuCAv7Qgn6cEvv4RFtaEs5OJrwKX6Z+Il5wdKaY2nX9GJfvwxpIWP6xTunxcO2vTZM iDAebN30LqVNIyI35rOTYOSBU7LRm81v+YNzqK3DWsd9S0Exz7UBOwlHWqJkPhKm8w+Eqw9noh3 DeEoHOMU7XcBTBdwFYsQ== X-Google-Smtp-Source: ABdhPJy2qhQkg+dt6IOc6A/m/zAIw8AKwR4VM4i6Fz7kbf5rs6LtPwit/cpovmxpQZeeZqSoUw70gg== X-Received: by 2002:a63:5fc8:: with SMTP id t191mr9082454pgb.361.1637205743396; Wed, 17 Nov 2021 19:22:23 -0800 (PST) Received: from ldt-sj3-komrakov.dhcp.broadcom.net ([192.19.222.250]) by smtp.gmail.com with ESMTPSA id z10sm1049654pfh.106.2021.11.17.19.22.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 17 Nov 2021 19:22:22 -0800 (PST) From: Alexander Komrakov To: linux-kernel@vger.kernel.org Cc: alexander.komrakov@broadcom.com Subject: [PATCH 1/1] Calculate the monotonic clock from the timespec clock to generate PPS elapsed real-time event value and stores the result into /sys/class/pps/pps0/assert_elapsed. Date: Wed, 17 Nov 2021 19:22:17 -0800 Message-Id: <20211118032217.244985-2-alexander.komrakov@broadcom.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20211118032217.244985-1-alexander.komrakov@broadcom.com> References: <20211118032217.244985-1-alexander.komrakov@broadcom.com> MIME-Version: 1.0 Content-Type: multipart/signed; protocol="application/pkcs7-signature"; micalg=sha-256; boundary="000000000000d32a3005d107aa84" Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org --000000000000d32a3005d107aa84 Content-Type: text/plain; charset="US-ASCII" From: alexkom Because we have requirements to make sure the delta between standard time, say the GPS Time, and elapsedRealtime < 1 millisecond, regular linux clock timestamp is not enough for our use case. The pin PPS will generate elapsedRealtime event at 1 sec boundary which is an exact value of the monotonic clock from the kernel PPS driver (/sys/class/pps/pps0/assert_elapsed). Whenever AP receives this pulse, kernel's pps driver timestamp this elapsedRealtime event and let this time available via sysfs node (/sys/class/pps/pps0/assert_elapsed). Signed-off-by: alexkom --- Documentation/ABI/testing/sysfs-pps | 24 ++++++++++++++++++ drivers/pps/kapi.c | 38 ++++++++++++++++++++++++++--- drivers/pps/sysfs.c | 33 +++++++++++++++++++++++++ include/linux/pps_kernel.h | 2 ++ 4 files changed, 94 insertions(+), 3 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-pps b/Documentation/ABI/testing/sysfs-pps index 25028c7bc37d..054617abeebd 100644 --- a/Documentation/ABI/testing/sysfs-pps +++ b/Documentation/ABI/testing/sysfs-pps @@ -1,3 +1,27 @@ +What: /sys/class/pps/pps0/assert_elapsed +Date: October 2021 +Contact: Alexander Komrakov +Description: + The /sys/class/pps/ppsX/assert_elapsed file reports the elapsed real-time assert events + and the elapsed real-time assert sequence number of the X-th source in the form: + + .# + + If the source has no elapsed real-time assert events the content of this file + is empty. + +What: /sys/class/pps/ppsX/clear_elapsed +Date: October 2021 +Contact: Alexander Komrakov +Description: + The /sys/class/pps/ppsX/clear_elapsed file reports the elapsed real-time clear events + and the elapsed real-time clear sequence number of the X-th source in the form: + + .# + + If the source has no elapsed real-time clear events the content of this file + is empty. + What: /sys/class/pps/ Date: February 2008 Contact: Rodolfo Giometti diff --git a/drivers/pps/kapi.c b/drivers/pps/kapi.c index d9d566f70ed1..149c29e498fc 100644 --- a/drivers/pps/kapi.c +++ b/drivers/pps/kapi.c @@ -23,6 +23,26 @@ /* * Local functions */ + #define NANOSEC_PER_SEC 1000000000 /* 10^9 */ + +/** + * clock_gettime - get the monotonic clock in pps_ktime format + * @kt: pointer to the pps_ktime to be set to raw monotonic time + * + * The function calculates the monotonic clock from the timespec clock + * and stores the result in pps_ktime format in the variable pointed to by @kt. + * + * The function returns the monotonic clock normalized format in nanosec. + */ +static __u64 clock_gettime(struct pps_ktime *kt) +{ + struct timespec64 ts = { .tv_sec = 0, .tv_nsec = 0 }; + + ktime_get_ts64(&ts); + kt->sec = ts.tv_sec; + kt->nsec = ts.tv_nsec; + return (__u64) ts.tv_sec * NANOSEC_PER_SEC + ts.tv_nsec; +} static void pps_add_offset(struct pps_ktime *ts, struct pps_ktime *offset) { @@ -162,11 +182,15 @@ void pps_event(struct pps_device *pps, struct pps_event_time *ts, int event, unsigned long flags; int captured = 0; struct pps_ktime ts_real = { .sec = 0, .nsec = 0, .flags = 0 }; + struct pps_ktime ts_real_elapsed = { .sec = 0, .nsec = 0, .flags = 0 }; /* check event type */ BUG_ON((event & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR)) == 0); + clock_gettime(&ts_real_elapsed); + dev_dbg(pps->dev, "PPS event (monotonic) at %lld.%09d\n", + (s64)ts_real_elapsed.sec, ts_real_elapsed.nsec); - dev_dbg(pps->dev, "PPS event at %lld.%09ld\n", + dev_dbg(pps->dev, "PPS event (timestamp) at %lld.%09ld\n", (s64)ts->ts_real.tv_sec, ts->ts_real.tv_nsec); timespec_to_pps_ktime(&ts_real, ts->ts_real); @@ -181,11 +205,15 @@ void pps_event(struct pps_device *pps, struct pps_event_time *ts, int event, pps->current_mode = pps->params.mode; if (event & pps->params.mode & PPS_CAPTUREASSERT) { /* We have to add an offset? */ - if (pps->params.mode & PPS_OFFSETASSERT) + if (pps->params.mode & PPS_OFFSETASSERT) { + pps_add_offset(&ts_real_elapsed, + &pps->params.assert_off_tu); pps_add_offset(&ts_real, &pps->params.assert_off_tu); + } /* Save the time stamp */ + pps->assert_elapsed_tu = ts_real_elapsed; pps->assert_tu = ts_real; pps->assert_sequence++; dev_dbg(pps->dev, "capture assert seq #%u\n", @@ -195,11 +223,15 @@ void pps_event(struct pps_device *pps, struct pps_event_time *ts, int event, } if (event & pps->params.mode & PPS_CAPTURECLEAR) { /* We have to add an offset? */ - if (pps->params.mode & PPS_OFFSETCLEAR) + if (pps->params.mode & PPS_OFFSETCLEAR) { + pps_add_offset(&ts_real_elapsed, + &pps->params.clear_off_tu); pps_add_offset(&ts_real, &pps->params.clear_off_tu); + } /* Save the time stamp */ + pps->clear_elapsed_tu = ts_real_elapsed; pps->clear_tu = ts_real; pps->clear_sequence++; dev_dbg(pps->dev, "capture clear seq #%u\n", diff --git a/drivers/pps/sysfs.c b/drivers/pps/sysfs.c index 134bc33f6ad0..97721cb01695 100644 --- a/drivers/pps/sysfs.c +++ b/drivers/pps/sysfs.c @@ -29,6 +29,21 @@ static ssize_t assert_show(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR_RO(assert); +static ssize_t assert_elapsed_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pps_device *pps = dev_get_drvdata(dev); + + if (!(pps->info.mode & PPS_CAPTUREASSERT)) + return 0; + + return sprintf(buf, "%lld.%09d#%d\n", + (long long) pps->assert_elapsed_tu.sec, + pps->assert_elapsed_tu.nsec, + pps->assert_sequence); +} +static DEVICE_ATTR_RO(assert_elapsed); + static ssize_t clear_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -43,6 +58,22 @@ static ssize_t clear_show(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR_RO(clear); +static ssize_t clear_elapsed_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct pps_device *pps = dev_get_drvdata(dev); + + if (!(pps->info.mode & PPS_CAPTURECLEAR)) + return 0; + + return sprintf(buf, "%lld.%09d#%d\n", + (long long) pps->clear_elapsed_tu.sec, + pps->clear_elapsed_tu.nsec, + pps->clear_sequence); +} +static DEVICE_ATTR_RO(clear_elapsed); + static ssize_t mode_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -81,7 +112,9 @@ static DEVICE_ATTR_RO(path); static struct attribute *pps_attrs[] = { &dev_attr_assert.attr, + &dev_attr_assert_elapsed.attr, &dev_attr_clear.attr, + &dev_attr_clear_elapsed.attr, &dev_attr_mode.attr, &dev_attr_echo.attr, &dev_attr_name.attr, diff --git a/include/linux/pps_kernel.h b/include/linux/pps_kernel.h index 78c8ac4951b5..1fecaaf4c8b9 100644 --- a/include/linux/pps_kernel.h +++ b/include/linux/pps_kernel.h @@ -47,6 +47,8 @@ struct pps_device { __u32 assert_sequence; /* PPS assert event seq # */ __u32 clear_sequence; /* PPS clear event seq # */ + struct pps_ktime assert_elapsed_tu; /* PPS elapsed rt assert seq # */ + struct pps_ktime clear_elapsed_tu; /* PPS elapsed rt clear event seq */ struct pps_ktime assert_tu; struct pps_ktime clear_tu; int current_mode; /* PPS mode at event time */ -- 2.25.1 -- This electronic communication and the information and any files transmitted with it, or attached to it, are confidential and are intended solely for the use of the individual or entity to whom it is addressed and may contain information that is confidential, legally privileged, protected by privacy laws, or otherwise restricted from disclosure to anyone else. If you are not the intended recipient or the person responsible for delivering the e-mail to the intended recipient, you are hereby notified that any use, copying, distributing, dissemination, forwarding, printing, or copying of this e-mail is strictly prohibited. If you received this e-mail in error, please return the e-mail to the sender, delete it from your computer, and destroy any printed copy of it. --000000000000d32a3005d107aa84 Content-Type: application/pkcs7-signature; name="smime.p7s" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="smime.p7s" Content-Description: S/MIME Cryptographic Signature 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 riBJ/K1AFUjRAjFhGV64l++td7dkmnq/X8ET75ti+w1s4FRpFqkD2m7pg5NxdsZphYIXAgMBAAGj QjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSP8Et/qC5FJK5N UPpjmove4t0bvDANBgkqhkiG9w0BAQsFAAOCAQEAS0DbwFCq/sgM7/eWVEVJu5YACUGssxOGhigH M8pr5nS5ugAtrqQK0/Xx8Q+Kv3NnSoPHRHt44K9ubG8DKY4zOUXDjuS5V2yq/BKW7FPGLeQkbLmU Y/vcU2hnVj6DuM81IcPJaP7O2sJTqsyQiunwXUaMld16WCgaLx3ezQA3QY/tRG3XUyiXfvNnBB4V 14qWtNPeTCekTBtzc3b0F5nCH3oO4y0IrQocLP88q1UOD5F+NuvDV0m+4S4tfGCLw0FREyOdzvcy a5QBqJnnLDMfOjsl0oZAzjsshnjJYS8Uuu7bVW/fhO4FCU29KNhyztNiUGUe65KXgzHZs7XKR1g/ XzCCBVkwggRBoAMCAQICDHYIL0hy7FtCa0iawzANBgkqhkiG9w0BAQsFADBbMQswCQYDVQQGEwJC RTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTExMC8GA1UEAxMoR2xvYmFsU2lnbiBHQ0MgUjMg UGVyc29uYWxTaWduIDIgQ0EgMjAyMDAeFw0yMTAyMjIwNDMzNTVaFw0yMjA5MDEwNzU5MzNaMIGV MQswCQYDVQQGEwJJTjESMBAGA1UECBMJS2FybmF0YWthMRIwEAYDVQQHEwlCYW5nYWxvcmUxFjAU BgNVBAoTDUJyb2FkY29tIEluYy4xFjAUBgNVBAMTDUFsZXggS29tcmFrb3YxLjAsBgkqhkiG9w0B CQEWH2FsZXhhbmRlci5rb21yYWtvdkBicm9hZGNvbS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB DwAwggEKAoIBAQC9plVHzugCEzdkg+8eZF4DLPZ5fqspSSVbjMcgMDJQcAR76/SGGJnSJiHOj/rn okK4r4HXW8cTMmw/ePqLs+eX7+h2TlrLFdwnPs6ThKSnKe7aNihCrk9rF+WyTTX/VrqyKPYICkp0 /XhRuIlIO0cP979rZRsxD4LKmC6x1msVkkM7JxkWhkktTzQwowAemtij6uzfYeh5BzQd2+LaWp8g ZX2NhNnwh9gNMFxHdE5c6+G3LG7AHwFOPA6G1TuzZ35urQXh4HWGbGoCJPszKLgccfOBBHYaXyo6 yiBn77ZVlo89La3IlKW/J8Bg1ZiYHcR6RtGGylxCCKgFDdESfV03AgMBAAGjggHgMIIB3DAOBgNV HQ8BAf8EBAMCBaAwgaMGCCsGAQUFBwEBBIGWMIGTME4GCCsGAQUFBzAChkJodHRwOi8vc2VjdXJl Lmdsb2JhbHNpZ24uY29tL2NhY2VydC9nc2djY3IzcGVyc29uYWxzaWduMmNhMjAyMC5jcnQwQQYI KwYBBQUHMAGGNWh0dHA6Ly9vY3NwLmdsb2JhbHNpZ24uY29tL2dzZ2NjcjNwZXJzb25hbHNpZ24y Y2EyMDIwME0GA1UdIARGMEQwQgYKKwYBBAGgMgEoCjA0MDIGCCsGAQUFBwIBFiZodHRwczovL3d3 dy5nbG9iYWxzaWduLmNvbS9yZXBvc2l0b3J5LzAJBgNVHRMEAjAAMEkGA1UdHwRCMEAwPqA8oDqG OGh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vZ3NnY2NyM3BlcnNvbmFsc2lnbjJjYTIwMjAuY3Js MCoGA1UdEQQjMCGBH2FsZXhhbmRlci5rb21yYWtvdkBicm9hZGNvbS5jb20wEwYDVR0lBAwwCgYI KwYBBQUHAwQwHwYDVR0jBBgwFoAUljPR5lgXWzR1ioFWZNW+SN6hj88wHQYDVR0OBBYEFElfe2CJ evQgM+vs7qmUiI/AGUBAMA0GCSqGSIb3DQEBCwUAA4IBAQCmTMsF9VHjT0L2ycGjBg8eb/+aTBhL U6r4e4vaGj/xmDd1cWfvz4brxodjpmuSnjfyWvU/odcNIepLv17Xc91OiZBWGYgr4jNViUqunvaH DCnJlLbrD88ITE1uo7OCdlN/SS+Sskp2dDvL/Xlyorb+PaS7/AaIwEmuGyJv2uv1wQ+UZzPXXo1B vOM4N+PxiEKCkmmYhfeSVye92Bta6vjf0b+oDE2JT82+D+9nAfiyJ9P/SRVTTvLlSzcO2fqX6GOc 37xY9F5HGjunD+cc5mqKM/r5PXyM/LEzWjdU1lVUVuvLRerUn+GNFgAPzpksTVYDv2kuseIFwRrF 845kQxaRMYICbTCCAmkCAQEwazBbMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBu di1zYTExMC8GA1UEAxMoR2xvYmFsU2lnbiBHQ0MgUjMgUGVyc29uYWxTaWduIDIgQ0EgMjAyMAIM dggvSHLsW0JrSJrDMA0GCWCGSAFlAwQCAQUAoIHUMC8GCSqGSIb3DQEJBDEiBCDyLZMuCKnZpWLu r4ySBH1BMzgJNvaFxMOEkUz4K8qrjTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3 DQEJBTEPFw0yMTExMTgwMzIyMjRaMGkGCSqGSIb3DQEJDzFcMFowCwYJYIZIAWUDBAEqMAsGCWCG SAFlAwQBFjALBglghkgBZQMEAQIwCgYIKoZIhvcNAwcwCwYJKoZIhvcNAQEKMAsGCSqGSIb3DQEB BzALBglghkgBZQMEAgEwDQYJKoZIhvcNAQEBBQAEggEAtj7iGUGZSJBiuK4O6I86iQggd34/b/9M C6k75wnjm2V4DEgXgk6OSIK+MetQzRPqKRJ9E5TsVf8OC9JBTlvFmgu/zs+zVelC0WX9C+SKuxhS trhFhem0ap1yrsTb59FbDlCE8aqH4lMqy9SpBp5RqrpvRwic/4kHo10ibeR1hkoGsZ0SC4KuT4uc hru2AbvomhJcAYYcfu2UGxw0ExMe/t337m4zRkgIFNo5xLEHDeabCe0QJdL8YBI+W2bWS5aBGtpe PLcOkct8pEfxNP17MZv/TtMzSanbkGOapbnzKDbZ5+MivA1q+BzogvX4EOxgckQXAinjtMuyBsre bYD0gQ== --000000000000d32a3005d107aa84--