Print this page
12011 ixgbe reports incorrect MAC_STAT_NORCVBUF
Change-Id: Ia71b5669b2bc8f6256a84b0b9c673153f327f5ab
*** 25,40 ****
/*
* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2012 Nexenta Systems, Inc. All rights reserved.
* Copyright 2016 OmniTI Computer Consulting, Inc. All rights reserved.
! * Copyright (c) 2017, Joyent, Inc.
*/
#include "ixgbe_sw.h"
/*
* Update driver private statistics.
*/
static int
ixgbe_update_stats(kstat_t *ks, int rw)
{
--- 25,170 ----
/*
* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2012 Nexenta Systems, Inc. All rights reserved.
* Copyright 2016 OmniTI Computer Consulting, Inc. All rights reserved.
! * Copyright 2019 Joyent, Inc.
*/
#include "ixgbe_sw.h"
/*
+ * The 82598 controller lacks a high/low register for the various
+ * octet counters, but the common code also lacks a definition for
+ * these older registers. In these cases, the high register address
+ * maps to the appropriate address in the 82598 controller.
+ */
+ #define IXGBE_TOR IXGBE_TORH
+ #define IXGBE_GOTC IXGBE_GOTCH
+ #define IXGBE_GORC IXGBE_GORCH
+
+ /*
+ * Read total octets received.
+ */
+ static uint64_t
+ ixgbe_read_tor_value(const struct ixgbe_hw *hw)
+ {
+ uint64_t tor = 0;
+ uint64_t hi = 0, lo = 0;
+
+ switch (hw->mac.type) {
+ case ixgbe_mac_82598EB:
+ tor = IXGBE_READ_REG(hw, IXGBE_TOR);
+ break;
+
+ default:
+ lo = IXGBE_READ_REG(hw, IXGBE_TORL);
+ hi = IXGBE_READ_REG(hw, IXGBE_TORH) & 0xF;
+ tor = (hi << 32) + lo;
+ break;
+ }
+
+ return (tor);
+ }
+
+ /*
+ * Read queue octets received.
+ */
+ static uint64_t
+ ixgbe_read_qor_value(const struct ixgbe_hw *hw)
+ {
+ uint64_t qor = 0;
+ uint64_t hi = 0, lo = 0;
+
+ switch (hw->mac.type) {
+ case ixgbe_mac_82598EB:
+ qor = IXGBE_READ_REG(hw, IXGBE_QBRC(0));
+ break;
+
+ default:
+ lo = IXGBE_READ_REG(hw, IXGBE_QBRC_L(0));
+ hi = IXGBE_READ_REG(hw, IXGBE_QBRC_H(0)) & 0xF;
+ qor = (hi << 32) + lo;
+ break;
+ }
+
+ return (qor);
+ }
+
+ /*
+ * Read queue octets transmitted.
+ */
+ static uint64_t
+ ixgbe_read_qot_value(const struct ixgbe_hw *hw)
+ {
+ uint64_t qot = 0;
+ uint64_t hi = 0, lo = 0;
+
+ switch (hw->mac.type) {
+ case ixgbe_mac_82598EB:
+ qot = IXGBE_READ_REG(hw, IXGBE_QBTC(0));
+ break;
+
+ default:
+ lo = IXGBE_READ_REG(hw, IXGBE_QBTC_L(0));
+ hi = IXGBE_READ_REG(hw, IXGBE_QBTC_H(0)) & 0xF;
+ qot = (hi << 32) + lo;
+ break;
+ }
+
+ return (qot);
+ }
+
+ /*
+ * Read good octets transmitted.
+ */
+ static uint64_t
+ ixgbe_read_got_value(const struct ixgbe_hw *hw)
+ {
+ uint64_t got = 0;
+ uint64_t hi = 0, lo = 0;
+
+ switch (hw->mac.type) {
+ case ixgbe_mac_82598EB:
+ got = IXGBE_READ_REG(hw, IXGBE_GOTC);
+ break;
+
+ default:
+ lo = IXGBE_READ_REG(hw, IXGBE_GOTCL);
+ hi = IXGBE_READ_REG(hw, IXGBE_GOTCH) & 0xF;
+ got = (hi << 32) + lo;
+ break;
+ }
+
+ return (got);
+ }
+
+ /*
+ * Read good octets received.
+ */
+ static uint64_t
+ ixgbe_read_gor_value(const struct ixgbe_hw *hw)
+ {
+ uint64_t gor = 0;
+ uint64_t hi = 0, lo = 0;
+
+ switch (hw->mac.type) {
+ case ixgbe_mac_82598EB:
+ gor = IXGBE_READ_REG(hw, IXGBE_GORC);
+ break;
+
+ default:
+ lo = IXGBE_READ_REG(hw, IXGBE_GORCL);
+ hi = IXGBE_READ_REG(hw, IXGBE_GORCH) & 0xF;
+ gor = (hi << 32) + lo;
+ break;
+ }
+
+ return (gor);
+ }
+
+ /*
* Update driver private statistics.
*/
static int
ixgbe_update_stats(kstat_t *ks, int rw)
{
*** 96,156 ****
}
/*
* Hardware calculated statistics.
*/
! ixgbe_ks->gprc.value.ui64 = 0;
! ixgbe_ks->gptc.value.ui64 = 0;
! ixgbe_ks->tor.value.ui64 = 0;
! ixgbe_ks->tot.value.ui64 = 0;
! for (i = 0; i < 16; i++) {
! ixgbe_ks->qprc[i].value.ui64 +=
! IXGBE_READ_REG(hw, IXGBE_QPRC(i));
! ixgbe_ks->gprc.value.ui64 += ixgbe_ks->qprc[i].value.ui64;
! ixgbe_ks->qptc[i].value.ui64 +=
! IXGBE_READ_REG(hw, IXGBE_QPTC(i));
! ixgbe_ks->gptc.value.ui64 += ixgbe_ks->qptc[i].value.ui64;
! ixgbe_ks->qbrc[i].value.ui64 +=
! IXGBE_READ_REG(hw, IXGBE_QBRC(i));
! ixgbe_ks->tor.value.ui64 += ixgbe_ks->qbrc[i].value.ui64;
! switch (hw->mac.type) {
! case ixgbe_mac_82598EB:
! ixgbe_ks->qbtc[i].value.ui64 +=
! IXGBE_READ_REG(hw, IXGBE_QBTC(i));
! break;
- case ixgbe_mac_82599EB:
- case ixgbe_mac_X540:
- case ixgbe_mac_X550:
- case ixgbe_mac_X550EM_x:
- case ixgbe_mac_X550EM_a:
- ixgbe_ks->qbtc[i].value.ui64 +=
- IXGBE_READ_REG(hw, IXGBE_QBTC_L(i));
- ixgbe_ks->qbtc[i].value.ui64 +=
- ((uint64_t)((IXGBE_READ_REG(hw,
- IXGBE_QBTC_H(i))) & 0xF) << 32);
- break;
-
- default:
- break;
- }
- ixgbe_ks->tot.value.ui64 += ixgbe_ks->qbtc[i].value.ui64;
- }
- /*
- * This is a Workaround:
- * Currently h/w GORCH, GOTCH, TORH registers are not
- * correctly implemented. We found that the values in
- * these registers are same as those in corresponding
- * *L registers (i.e. GORCL, GOTCL, and TORL). Here the
- * gor and got stat data will not be retrieved through
- * GORC{H/L} and GOTC{H/L} registers but be obtained by
- * simply assigning tor/tot stat data, so the gor/got
- * stat data will not be accurate.
- */
- ixgbe_ks->gor.value.ui64 = ixgbe_ks->tor.value.ui64;
- ixgbe_ks->got.value.ui64 = ixgbe_ks->tot.value.ui64;
-
ixgbe_ks->prc64.value.ul += IXGBE_READ_REG(hw, IXGBE_PRC64);
ixgbe_ks->prc127.value.ul += IXGBE_READ_REG(hw, IXGBE_PRC127);
ixgbe_ks->prc255.value.ul += IXGBE_READ_REG(hw, IXGBE_PRC255);
ixgbe_ks->prc511.value.ul += IXGBE_READ_REG(hw, IXGBE_PRC511);
ixgbe_ks->prc1023.value.ul += IXGBE_READ_REG(hw, IXGBE_PRC1023);
--- 226,246 ----
}
/*
* Hardware calculated statistics.
*/
! ixgbe_ks->gprc.value.ui64 += IXGBE_READ_REG(hw, IXGBE_GPRC);
! ixgbe_ks->gptc.value.ui64 += IXGBE_READ_REG(hw, IXGBE_GPTC);
! ixgbe_ks->gor.value.ui64 += ixgbe_read_gor_value(hw);
! ixgbe_ks->got.value.ui64 += ixgbe_read_got_value(hw);
! ixgbe_ks->qpr.value.ui64 += IXGBE_READ_REG(hw, IXGBE_QPRC(0));
! ixgbe_ks->qpt.value.ui64 += IXGBE_READ_REG(hw, IXGBE_QPTC(0));
! ixgbe_ks->qor.value.ui64 += ixgbe_read_qor_value(hw);
! ixgbe_ks->qot.value.ui64 += ixgbe_read_qot_value(hw);
! ixgbe_ks->tor.value.ui64 += ixgbe_read_tor_value(hw);
! ixgbe_ks->tot.value.ui64 = ixgbe_ks->got.value.ui64;
ixgbe_ks->prc64.value.ul += IXGBE_READ_REG(hw, IXGBE_PRC64);
ixgbe_ks->prc127.value.ul += IXGBE_READ_REG(hw, IXGBE_PRC127);
ixgbe_ks->prc255.value.ul += IXGBE_READ_REG(hw, IXGBE_PRC255);
ixgbe_ks->prc511.value.ul += IXGBE_READ_REG(hw, IXGBE_PRC511);
ixgbe_ks->prc1023.value.ul += IXGBE_READ_REG(hw, IXGBE_PRC1023);
*** 280,289 ****
--- 370,387 ----
KSTAT_DATA_UINT64);
kstat_named_init(&ixgbe_ks->gor, "good_octets_recvd",
KSTAT_DATA_UINT64);
kstat_named_init(&ixgbe_ks->got, "good_octets_xmitd",
KSTAT_DATA_UINT64);
+ kstat_named_init(&ixgbe_ks->qor, "queue_octets_recvd",
+ KSTAT_DATA_UINT64);
+ kstat_named_init(&ixgbe_ks->qot, "queue_octets_xmitd",
+ KSTAT_DATA_UINT64);
+ kstat_named_init(&ixgbe_ks->qpr, "queue_pkts_recvd",
+ KSTAT_DATA_UINT64);
+ kstat_named_init(&ixgbe_ks->qpt, "queue_pkts_xmitd",
+ KSTAT_DATA_UINT64);
kstat_named_init(&ixgbe_ks->prc64, "pkts_recvd_( 64b)",
KSTAT_DATA_UINT64);
kstat_named_init(&ixgbe_ks->prc127, "pkts_recvd_( 65- 127b)",
KSTAT_DATA_UINT64);
kstat_named_init(&ixgbe_ks->prc255, "pkts_recvd_( 127- 255b)",
*** 305,446 ****
kstat_named_init(&ixgbe_ks->ptc1023, "pkts_xmitd_( 512-1023b)",
KSTAT_DATA_UINT64);
kstat_named_init(&ixgbe_ks->ptc1522, "pkts_xmitd_(1024-1522b)",
KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qprc[0], "queue_pkts_recvd [ 0]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qprc[1], "queue_pkts_recvd [ 1]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qprc[2], "queue_pkts_recvd [ 2]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qprc[3], "queue_pkts_recvd [ 3]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qprc[4], "queue_pkts_recvd [ 4]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qprc[5], "queue_pkts_recvd [ 5]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qprc[6], "queue_pkts_recvd [ 6]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qprc[7], "queue_pkts_recvd [ 7]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qprc[8], "queue_pkts_recvd [ 8]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qprc[9], "queue_pkts_recvd [ 9]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qprc[10], "queue_pkts_recvd [10]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qprc[11], "queue_pkts_recvd [11]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qprc[12], "queue_pkts_recvd [12]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qprc[13], "queue_pkts_recvd [13]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qprc[14], "queue_pkts_recvd [14]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qprc[15], "queue_pkts_recvd [15]",
- KSTAT_DATA_UINT64);
-
- kstat_named_init(&ixgbe_ks->qptc[0], "queue_pkts_xmitd [ 0]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qptc[1], "queue_pkts_xmitd [ 1]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qptc[2], "queue_pkts_xmitd [ 2]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qptc[3], "queue_pkts_xmitd [ 3]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qptc[4], "queue_pkts_xmitd [ 4]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qptc[5], "queue_pkts_xmitd [ 5]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qptc[6], "queue_pkts_xmitd [ 6]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qptc[7], "queue_pkts_xmitd [ 7]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qptc[8], "queue_pkts_xmitd [ 8]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qptc[9], "queue_pkts_xmitd [ 9]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qptc[10], "queue_pkts_xmitd [10]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qptc[11], "queue_pkts_xmitd [11]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qptc[12], "queue_pkts_xmitd [12]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qptc[13], "queue_pkts_xmitd [13]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qptc[14], "queue_pkts_xmitd [14]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qptc[15], "queue_pkts_xmitd [15]",
- KSTAT_DATA_UINT64);
-
- kstat_named_init(&ixgbe_ks->qbrc[0], "queue_bytes_recvd [ 0]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbrc[1], "queue_bytes_recvd [ 1]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbrc[2], "queue_bytes_recvd [ 2]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbrc[3], "queue_bytes_recvd [ 3]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbrc[4], "queue_bytes_recvd [ 4]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbrc[5], "queue_bytes_recvd [ 5]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbrc[6], "queue_bytes_recvd [ 6]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbrc[7], "queue_bytes_recvd [ 7]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbrc[8], "queue_bytes_recvd [ 8]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbrc[9], "queue_bytes_recvd [ 9]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbrc[10], "queue_bytes_recvd [10]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbrc[11], "queue_bytes_recvd [11]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbrc[12], "queue_bytes_recvd [12]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbrc[13], "queue_bytes_recvd [13]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbrc[14], "queue_bytes_recvd [14]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbrc[15], "queue_bytes_recvd [15]",
- KSTAT_DATA_UINT64);
-
- kstat_named_init(&ixgbe_ks->qbtc[0], "queue_bytes_xmitd [ 0]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbtc[1], "queue_bytes_xmitd [ 1]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbtc[2], "queue_bytes_xmitd [ 2]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbtc[3], "queue_bytes_xmitd [ 3]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbtc[4], "queue_bytes_xmitd [ 4]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbtc[5], "queue_bytes_xmitd [ 5]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbtc[6], "queue_bytes_xmitd [ 6]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbtc[7], "queue_bytes_xmitd [ 7]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbtc[8], "queue_bytes_xmitd [ 8]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbtc[9], "queue_bytes_xmitd [ 9]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbtc[10], "queue_bytes_xmitd [10]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbtc[11], "queue_bytes_xmitd [11]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbtc[12], "queue_bytes_xmitd [12]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbtc[13], "queue_bytes_xmitd [13]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbtc[14], "queue_bytes_xmitd [14]",
- KSTAT_DATA_UINT64);
- kstat_named_init(&ixgbe_ks->qbtc[15], "queue_bytes_xmitd [15]",
- KSTAT_DATA_UINT64);
-
kstat_named_init(&ixgbe_ks->mspdc, "mac_short_packet_discard",
KSTAT_DATA_UINT64);
kstat_named_init(&ixgbe_ks->mpc, "missed_packets",
KSTAT_DATA_UINT64);
kstat_named_init(&ixgbe_ks->mlfc, "mac_local_fault",
--- 403,412 ----
*** 546,559 ****
--- 512,536 ----
IXGBE_READ_REG(hw, IXGBE_BPTC);
*val = ixgbe_ks->bptc.value.ui64;
break;
case MAC_STAT_NORCVBUF:
+ /*
+ * The QPRDC[0] register maps to the same kstat as the
+ * old RNBC register because they have equivalent
+ * semantics.
+ */
+ if (hw->mac.type == ixgbe_mac_82598EB) {
for (i = 0; i < 8; i++) {
ixgbe_ks->rnbc.value.ui64 +=
IXGBE_READ_REG(hw, IXGBE_RNBC(i));
}
+ } else {
+ ixgbe_ks->rnbc.value.ui64 +=
+ IXGBE_READ_REG(hw, IXGBE_QPRDC(0));
+ }
+
*val = ixgbe_ks->rnbc.value.ui64;
break;
case MAC_STAT_IERRORS:
ixgbe_ks->crcerrs.value.ui64 +=
*** 569,615 ****
ixgbe_ks->errbc.value.ui64 +
ixgbe_ks->rlec.value.ui64;
break;
case MAC_STAT_RBYTES:
! ixgbe_ks->tor.value.ui64 = 0;
! for (i = 0; i < 16; i++) {
! ixgbe_ks->qbrc[i].value.ui64 +=
! IXGBE_READ_REG(hw, IXGBE_QBRC(i));
! ixgbe_ks->tor.value.ui64 +=
! ixgbe_ks->qbrc[i].value.ui64;
! }
*val = ixgbe_ks->tor.value.ui64;
break;
case MAC_STAT_OBYTES:
! ixgbe_ks->tot.value.ui64 = 0;
! for (i = 0; i < 16; i++) {
! switch (hw->mac.type) {
! case ixgbe_mac_82598EB:
! ixgbe_ks->qbtc[i].value.ui64 +=
! IXGBE_READ_REG(hw, IXGBE_QBTC(i));
! break;
!
! case ixgbe_mac_82599EB:
! case ixgbe_mac_X540:
! case ixgbe_mac_X550:
! case ixgbe_mac_X550EM_x:
! case ixgbe_mac_X550EM_a:
! ixgbe_ks->qbtc[i].value.ui64 +=
! IXGBE_READ_REG(hw, IXGBE_QBTC_L(i));
! ixgbe_ks->qbtc[i].value.ui64 +=
! ((uint64_t)((IXGBE_READ_REG(hw,
! IXGBE_QBTC_H(i))) & 0xF) << 32);
! break;
!
! default:
! break;
! }
! ixgbe_ks->tot.value.ui64 +=
! ixgbe_ks->qbtc[i].value.ui64;
! }
*val = ixgbe_ks->tot.value.ui64;
break;
case MAC_STAT_IPACKETS:
ixgbe_ks->tpr.value.ui64 +=
--- 546,569 ----
ixgbe_ks->errbc.value.ui64 +
ixgbe_ks->rlec.value.ui64;
break;
case MAC_STAT_RBYTES:
! ixgbe_ks->tor.value.ui64 += ixgbe_read_tor_value(hw);
*val = ixgbe_ks->tor.value.ui64;
break;
case MAC_STAT_OBYTES:
! /*
! * The controller does not provide a Total Octets
! * Transmitted statistic. The closest thing we have is
! * Good Octets Transmitted. This makes sense, as what
! * does it mean to transmit a packet if it didn't
! * actually transmit.
! */
! ixgbe_ks->got.value.ui64 += ixgbe_read_got_value(hw);
! ixgbe_ks->tot.value.ui64 = ixgbe_ks->got.value.ui64;
*val = ixgbe_ks->tot.value.ui64;
break;
case MAC_STAT_IPACKETS:
ixgbe_ks->tpr.value.ui64 +=