diff options
| author | Shawn Bohrer <sbohrer@rgmadvisors.com> | 2013-12-31 12:39:39 -0500 |
|---|---|---|
| committer | David S. Miller <davem@davemloft.net> | 2014-01-02 03:30:36 -0500 |
| commit | ad7d4eaed995d76fb24a18e202fdf5072197ff0a (patch) | |
| tree | 2625d165dd5b6972f582f19e20cc6c955fd9259a | |
| parent | dd9b45598a7198f8b12965f2ec453bcb5cb90aec (diff) | |
mlx4_en: Add PTP hardware clock
This adds a PHC to the mlx4_en driver. We use reader/writer spinlocks to
protect the timecounter since every packet received needs to call
timecounter_cycle2time() when timestamping is enabled. This can become
a performance bottleneck with RSS and multiple receive queues if normal
spinlocks are used.
This driver has been tested with both Documentation/ptp/testptp and the
linuxptp project (http://linuxptp.sourceforge.net/) on a Mellanox
ConnectX-3 card.
Signed-off-by: Shawn Bohrer <sbohrer@rgmadvisors.com>
Acked-By: Hadar Hen Zion <hadarh@mellanox.com>
Acked-by: Richard Cochran <richardcochran@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
| -rw-r--r-- | drivers/net/ethernet/mellanox/mlx4/en_clock.c | 194 | ||||
| -rw-r--r-- | drivers/net/ethernet/mellanox/mlx4/en_ethtool.c | 3 | ||||
| -rw-r--r-- | drivers/net/ethernet/mellanox/mlx4/en_main.c | 3 | ||||
| -rw-r--r-- | drivers/net/ethernet/mellanox/mlx4/mlx4_en.h | 6 |
4 files changed, 198 insertions, 8 deletions
diff --git a/drivers/net/ethernet/mellanox/mlx4/en_clock.c b/drivers/net/ethernet/mellanox/mlx4/en_clock.c index fd6441071319..30712b36f43c 100644 --- a/drivers/net/ethernet/mellanox/mlx4/en_clock.c +++ b/drivers/net/ethernet/mellanox/mlx4/en_clock.c | |||
| @@ -103,19 +103,191 @@ void mlx4_en_fill_hwtstamps(struct mlx4_en_dev *mdev, | |||
| 103 | struct skb_shared_hwtstamps *hwts, | 103 | struct skb_shared_hwtstamps *hwts, |
| 104 | u64 timestamp) | 104 | u64 timestamp) |
| 105 | { | 105 | { |
| 106 | unsigned long flags; | ||
| 106 | u64 nsec; | 107 | u64 nsec; |
| 107 | 108 | ||
| 109 | read_lock_irqsave(&mdev->clock_lock, flags); | ||
| 108 | nsec = timecounter_cyc2time(&mdev->clock, timestamp); | 110 | nsec = timecounter_cyc2time(&mdev->clock, timestamp); |
| 111 | read_unlock_irqrestore(&mdev->clock_lock, flags); | ||
| 109 | 112 | ||
| 110 | memset(hwts, 0, sizeof(struct skb_shared_hwtstamps)); | 113 | memset(hwts, 0, sizeof(struct skb_shared_hwtstamps)); |
| 111 | hwts->hwtstamp = ns_to_ktime(nsec); | 114 | hwts->hwtstamp = ns_to_ktime(nsec); |
| 112 | } | 115 | } |
| 113 | 116 | ||
| 117 | /** | ||
| 118 | * mlx4_en_remove_timestamp - disable PTP device | ||
| 119 | * @mdev: board private structure | ||
| 120 | * | ||
| 121 | * Stop the PTP support. | ||
| 122 | **/ | ||
| 123 | void mlx4_en_remove_timestamp(struct mlx4_en_dev *mdev) | ||
| 124 | { | ||
| 125 | if (mdev->ptp_clock) { | ||
| 126 | ptp_clock_unregister(mdev->ptp_clock); | ||
| 127 | mdev->ptp_clock = NULL; | ||
| 128 | mlx4_info(mdev, "removed PHC\n"); | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | void mlx4_en_ptp_overflow_check(struct mlx4_en_dev *mdev) | ||
| 133 | { | ||
| 134 | bool timeout = time_is_before_jiffies(mdev->last_overflow_check + | ||
| 135 | mdev->overflow_period); | ||
| 136 | unsigned long flags; | ||
| 137 | |||
| 138 | if (timeout) { | ||
| 139 | write_lock_irqsave(&mdev->clock_lock, flags); | ||
| 140 | timecounter_read(&mdev->clock); | ||
| 141 | write_unlock_irqrestore(&mdev->clock_lock, flags); | ||
| 142 | mdev->last_overflow_check = jiffies; | ||
| 143 | } | ||
| 144 | } | ||
| 145 | |||
| 146 | /** | ||
| 147 | * mlx4_en_phc_adjfreq - adjust the frequency of the hardware clock | ||
| 148 | * @ptp: ptp clock structure | ||
| 149 | * @delta: Desired frequency change in parts per billion | ||
| 150 | * | ||
| 151 | * Adjust the frequency of the PHC cycle counter by the indicated delta from | ||
| 152 | * the base frequency. | ||
| 153 | **/ | ||
| 154 | static int mlx4_en_phc_adjfreq(struct ptp_clock_info *ptp, s32 delta) | ||
| 155 | { | ||
| 156 | u64 adj; | ||
| 157 | u32 diff, mult; | ||
| 158 | int neg_adj = 0; | ||
| 159 | unsigned long flags; | ||
| 160 | struct mlx4_en_dev *mdev = container_of(ptp, struct mlx4_en_dev, | ||
| 161 | ptp_clock_info); | ||
| 162 | |||
| 163 | if (delta < 0) { | ||
| 164 | neg_adj = 1; | ||
| 165 | delta = -delta; | ||
| 166 | } | ||
| 167 | mult = mdev->nominal_c_mult; | ||
| 168 | adj = mult; | ||
| 169 | adj *= delta; | ||
| 170 | diff = div_u64(adj, 1000000000ULL); | ||
| 171 | |||
| 172 | write_lock_irqsave(&mdev->clock_lock, flags); | ||
| 173 | timecounter_read(&mdev->clock); | ||
| 174 | mdev->cycles.mult = neg_adj ? mult - diff : mult + diff; | ||
| 175 | write_unlock_irqrestore(&mdev->clock_lock, flags); | ||
| 176 | |||
| 177 | return 0; | ||
| 178 | } | ||
| 179 | |||
| 180 | /** | ||
| 181 | * mlx4_en_phc_adjtime - Shift the time of the hardware clock | ||
| 182 | * @ptp: ptp clock structure | ||
| 183 | * @delta: Desired change in nanoseconds | ||
| 184 | * | ||
| 185 | * Adjust the timer by resetting the timecounter structure. | ||
| 186 | **/ | ||
| 187 | static int mlx4_en_phc_adjtime(struct ptp_clock_info *ptp, s64 delta) | ||
| 188 | { | ||
| 189 | struct mlx4_en_dev *mdev = container_of(ptp, struct mlx4_en_dev, | ||
| 190 | ptp_clock_info); | ||
| 191 | unsigned long flags; | ||
| 192 | s64 now; | ||
| 193 | |||
| 194 | write_lock_irqsave(&mdev->clock_lock, flags); | ||
| 195 | now = timecounter_read(&mdev->clock); | ||
| 196 | now += delta; | ||
| 197 | timecounter_init(&mdev->clock, &mdev->cycles, now); | ||
| 198 | write_unlock_irqrestore(&mdev->clock_lock, flags); | ||
| 199 | |||
| 200 | return 0; | ||
| 201 | } | ||
| 202 | |||
| 203 | /** | ||
| 204 | * mlx4_en_phc_gettime - Reads the current time from the hardware clock | ||
| 205 | * @ptp: ptp clock structure | ||
| 206 | * @ts: timespec structure to hold the current time value | ||
| 207 | * | ||
| 208 | * Read the timecounter and return the correct value in ns after converting | ||
| 209 | * it into a struct timespec. | ||
| 210 | **/ | ||
| 211 | static int mlx4_en_phc_gettime(struct ptp_clock_info *ptp, struct timespec *ts) | ||
| 212 | { | ||
| 213 | struct mlx4_en_dev *mdev = container_of(ptp, struct mlx4_en_dev, | ||
| 214 | ptp_clock_info); | ||
| 215 | unsigned long flags; | ||
| 216 | u32 remainder; | ||
| 217 | u64 ns; | ||
| 218 | |||
| 219 | write_lock_irqsave(&mdev->clock_lock, flags); | ||
| 220 | ns = timecounter_read(&mdev->clock); | ||
| 221 | write_unlock_irqrestore(&mdev->clock_lock, flags); | ||
| 222 | |||
| 223 | ts->tv_sec = div_u64_rem(ns, NSEC_PER_SEC, &remainder); | ||
| 224 | ts->tv_nsec = remainder; | ||
| 225 | |||
| 226 | return 0; | ||
| 227 | } | ||
| 228 | |||
| 229 | /** | ||
| 230 | * mlx4_en_phc_settime - Set the current time on the hardware clock | ||
| 231 | * @ptp: ptp clock structure | ||
| 232 | * @ts: timespec containing the new time for the cycle counter | ||
| 233 | * | ||
| 234 | * Reset the timecounter to use a new base value instead of the kernel | ||
| 235 | * wall timer value. | ||
| 236 | **/ | ||
| 237 | static int mlx4_en_phc_settime(struct ptp_clock_info *ptp, | ||
| 238 | const struct timespec *ts) | ||
| 239 | { | ||
| 240 | struct mlx4_en_dev *mdev = container_of(ptp, struct mlx4_en_dev, | ||
| 241 | ptp_clock_info); | ||
| 242 | u64 ns = timespec_to_ns(ts); | ||
| 243 | unsigned long flags; | ||
| 244 | |||
| 245 | /* reset the timecounter */ | ||
| 246 | write_lock_irqsave(&mdev->clock_lock, flags); | ||
| 247 | timecounter_init(&mdev->clock, &mdev->cycles, ns); | ||
| 248 | write_unlock_irqrestore(&mdev->clock_lock, flags); | ||
| 249 | |||
| 250 | return 0; | ||
| 251 | } | ||
| 252 | |||
| 253 | /** | ||
| 254 | * mlx4_en_phc_enable - enable or disable an ancillary feature | ||
| 255 | * @ptp: ptp clock structure | ||
| 256 | * @request: Desired resource to enable or disable | ||
| 257 | * @on: Caller passes one to enable or zero to disable | ||
| 258 | * | ||
| 259 | * Enable (or disable) ancillary features of the PHC subsystem. | ||
| 260 | * Currently, no ancillary features are supported. | ||
| 261 | **/ | ||
| 262 | static int mlx4_en_phc_enable(struct ptp_clock_info __always_unused *ptp, | ||
| 263 | struct ptp_clock_request __always_unused *request, | ||
| 264 | int __always_unused on) | ||
| 265 | { | ||
| 266 | return -EOPNOTSUPP; | ||
| 267 | } | ||
| 268 | |||
| 269 | static const struct ptp_clock_info mlx4_en_ptp_clock_info = { | ||
| 270 | .owner = THIS_MODULE, | ||
| 271 | .max_adj = 100000000, | ||
| 272 | .n_alarm = 0, | ||
| 273 | .n_ext_ts = 0, | ||
| 274 | .n_per_out = 0, | ||
| 275 | .pps = 0, | ||
| 276 | .adjfreq = mlx4_en_phc_adjfreq, | ||
| 277 | .adjtime = mlx4_en_phc_adjtime, | ||
| 278 | .gettime = mlx4_en_phc_gettime, | ||
| 279 | .settime = mlx4_en_phc_settime, | ||
| 280 | .enable = mlx4_en_phc_enable, | ||
| 281 | }; | ||
| 282 | |||
| 114 | void mlx4_en_init_timestamp(struct mlx4_en_dev *mdev) | 283 | void mlx4_en_init_timestamp(struct mlx4_en_dev *mdev) |
| 115 | { | 284 | { |
| 116 | struct mlx4_dev *dev = mdev->dev; | 285 | struct mlx4_dev *dev = mdev->dev; |
| 286 | unsigned long flags; | ||
| 117 | u64 ns; | 287 | u64 ns; |
| 118 | 288 | ||
| 289 | rwlock_init(&mdev->clock_lock); | ||
| 290 | |||
| 119 | memset(&mdev->cycles, 0, sizeof(mdev->cycles)); | 291 | memset(&mdev->cycles, 0, sizeof(mdev->cycles)); |
| 120 | mdev->cycles.read = mlx4_en_read_clock; | 292 | mdev->cycles.read = mlx4_en_read_clock; |
| 121 | mdev->cycles.mask = CLOCKSOURCE_MASK(48); | 293 | mdev->cycles.mask = CLOCKSOURCE_MASK(48); |
| @@ -127,9 +299,12 @@ void mlx4_en_init_timestamp(struct mlx4_en_dev *mdev) | |||
| 127 | mdev->cycles.shift = 14; | 299 | mdev->cycles.shift = 14; |
| 128 | mdev->cycles.mult = | 300 | mdev->cycles.mult = |
| 129 | clocksource_khz2mult(1000 * dev->caps.hca_core_clock, mdev->cycles.shift); | 301 | clocksource_khz2mult(1000 * dev->caps.hca_core_clock, mdev->cycles.shift); |
| 302 | mdev->nominal_c_mult = mdev->cycles.mult; | ||
| 130 | 303 | ||
| 304 | write_lock_irqsave(&mdev->clock_lock, flags); | ||
| 131 | timecounter_init(&mdev->clock, &mdev->cycles, | 305 | timecounter_init(&mdev->clock, &mdev->cycles, |
| 132 | ktime_to_ns(ktime_get_real())); | 306 | ktime_to_ns(ktime_get_real())); |
| 307 | write_unlock_irqrestore(&mdev->clock_lock, flags); | ||
| 133 | 308 | ||
| 134 | /* Calculate period in seconds to call the overflow watchdog - to make | 309 | /* Calculate period in seconds to call the overflow watchdog - to make |
| 135 | * sure counter is checked at least once every wrap around. | 310 | * sure counter is checked at least once every wrap around. |
| @@ -137,15 +312,18 @@ void mlx4_en_init_timestamp(struct mlx4_en_dev *mdev) | |||
| 137 | ns = cyclecounter_cyc2ns(&mdev->cycles, mdev->cycles.mask); | 312 | ns = cyclecounter_cyc2ns(&mdev->cycles, mdev->cycles.mask); |
| 138 | do_div(ns, NSEC_PER_SEC / 2 / HZ); | 313 | do_div(ns, NSEC_PER_SEC / 2 / HZ); |
| 139 | mdev->overflow_period = ns; | 314 | mdev->overflow_period = ns; |
| 140 | } | ||
| 141 | 315 | ||
| 142 | void mlx4_en_ptp_overflow_check(struct mlx4_en_dev *mdev) | 316 | /* Configure the PHC */ |
| 143 | { | 317 | mdev->ptp_clock_info = mlx4_en_ptp_clock_info; |
| 144 | bool timeout = time_is_before_jiffies(mdev->last_overflow_check + | 318 | snprintf(mdev->ptp_clock_info.name, 16, "mlx4 ptp"); |
| 145 | mdev->overflow_period); | ||
| 146 | 319 | ||
| 147 | if (timeout) { | 320 | mdev->ptp_clock = ptp_clock_register(&mdev->ptp_clock_info, |
| 148 | timecounter_read(&mdev->clock); | 321 | &mdev->pdev->dev); |
| 149 | mdev->last_overflow_check = jiffies; | 322 | if (IS_ERR(mdev->ptp_clock)) { |
| 323 | mdev->ptp_clock = NULL; | ||
| 324 | mlx4_err(mdev, "ptp_clock_register failed\n"); | ||
| 325 | } else { | ||
| 326 | mlx4_info(mdev, "registered PHC clock\n"); | ||
| 150 | } | 327 | } |
| 328 | |||
| 151 | } | 329 | } |
diff --git a/drivers/net/ethernet/mellanox/mlx4/en_ethtool.c b/drivers/net/ethernet/mellanox/mlx4/en_ethtool.c index 0596f9f85a0e..3e8d33605fe7 100644 --- a/drivers/net/ethernet/mellanox/mlx4/en_ethtool.c +++ b/drivers/net/ethernet/mellanox/mlx4/en_ethtool.c | |||
| @@ -1193,6 +1193,9 @@ static int mlx4_en_get_ts_info(struct net_device *dev, | |||
| 1193 | info->rx_filters = | 1193 | info->rx_filters = |
| 1194 | (1 << HWTSTAMP_FILTER_NONE) | | 1194 | (1 << HWTSTAMP_FILTER_NONE) | |
| 1195 | (1 << HWTSTAMP_FILTER_ALL); | 1195 | (1 << HWTSTAMP_FILTER_ALL); |
| 1196 | |||
| 1197 | if (mdev->ptp_clock) | ||
| 1198 | info->phc_index = ptp_clock_index(mdev->ptp_clock); | ||
| 1196 | } | 1199 | } |
| 1197 | 1200 | ||
| 1198 | return ret; | 1201 | return ret; |
diff --git a/drivers/net/ethernet/mellanox/mlx4/en_main.c b/drivers/net/ethernet/mellanox/mlx4/en_main.c index 725a4e1b5f67..d357bf5a4686 100644 --- a/drivers/net/ethernet/mellanox/mlx4/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx4/en_main.c | |||
| @@ -199,6 +199,9 @@ static void mlx4_en_remove(struct mlx4_dev *dev, void *endev_ptr) | |||
| 199 | if (mdev->pndev[i]) | 199 | if (mdev->pndev[i]) |
| 200 | mlx4_en_destroy_netdev(mdev->pndev[i]); | 200 | mlx4_en_destroy_netdev(mdev->pndev[i]); |
| 201 | 201 | ||
| 202 | if (mdev->dev->caps.flags2 & MLX4_DEV_CAP_FLAG2_TS) | ||
| 203 | mlx4_en_remove_timestamp(mdev); | ||
| 204 | |||
| 202 | flush_workqueue(mdev->workqueue); | 205 | flush_workqueue(mdev->workqueue); |
| 203 | destroy_workqueue(mdev->workqueue); | 206 | destroy_workqueue(mdev->workqueue); |
| 204 | (void) mlx4_mr_free(dev, &mdev->mr); | 207 | (void) mlx4_mr_free(dev, &mdev->mr); |
diff --git a/drivers/net/ethernet/mellanox/mlx4/mlx4_en.h b/drivers/net/ethernet/mellanox/mlx4/mlx4_en.h index 766691c66111..2f1e200f2e4c 100644 --- a/drivers/net/ethernet/mellanox/mlx4/mlx4_en.h +++ b/drivers/net/ethernet/mellanox/mlx4/mlx4_en.h | |||
| @@ -45,6 +45,7 @@ | |||
| 45 | #include <linux/dcbnl.h> | 45 | #include <linux/dcbnl.h> |
| 46 | #endif | 46 | #endif |
| 47 | #include <linux/cpu_rmap.h> | 47 | #include <linux/cpu_rmap.h> |
| 48 | #include <linux/ptp_clock_kernel.h> | ||
| 48 | 49 | ||
| 49 | #include <linux/mlx4/device.h> | 50 | #include <linux/mlx4/device.h> |
| 50 | #include <linux/mlx4/qp.h> | 51 | #include <linux/mlx4/qp.h> |
| @@ -375,10 +376,14 @@ struct mlx4_en_dev { | |||
| 375 | u32 priv_pdn; | 376 | u32 priv_pdn; |
| 376 | spinlock_t uar_lock; | 377 | spinlock_t uar_lock; |
| 377 | u8 mac_removed[MLX4_MAX_PORTS + 1]; | 378 | u8 mac_removed[MLX4_MAX_PORTS + 1]; |
| 379 | rwlock_t clock_lock; | ||
| 380 | u32 nominal_c_mult; | ||
| 378 | struct cyclecounter cycles; | 381 | struct cyclecounter cycles; |
| 379 | struct timecounter clock; | 382 | struct timecounter clock; |
| 380 | unsigned long last_overflow_check; | 383 | unsigned long last_overflow_check; |
| 381 | unsigned long overflow_period; | 384 | unsigned long overflow_period; |
| 385 | struct ptp_clock *ptp_clock; | ||
| 386 | struct ptp_clock_info ptp_clock_info; | ||
| 382 | }; | 387 | }; |
| 383 | 388 | ||
| 384 | 389 | ||
| @@ -791,6 +796,7 @@ void mlx4_en_fill_hwtstamps(struct mlx4_en_dev *mdev, | |||
| 791 | struct skb_shared_hwtstamps *hwts, | 796 | struct skb_shared_hwtstamps *hwts, |
| 792 | u64 timestamp); | 797 | u64 timestamp); |
| 793 | void mlx4_en_init_timestamp(struct mlx4_en_dev *mdev); | 798 | void mlx4_en_init_timestamp(struct mlx4_en_dev *mdev); |
| 799 | void mlx4_en_remove_timestamp(struct mlx4_en_dev *mdev); | ||
| 794 | int mlx4_en_timestamp_config(struct net_device *dev, | 800 | int mlx4_en_timestamp_config(struct net_device *dev, |
| 795 | int tx_type, | 801 | int tx_type, |
| 796 | int rx_filter); | 802 | int rx_filter); |
