LCOV - code coverage report
Current view: top level - drivers/net/zxdh - zxdh_queue.c (source / functions) Hit Total Coverage
Test: Code coverage Lines: 0 195 0.0 %
Date: 2025-04-03 19:37:06 Functions: 0 13 0.0 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 0 88 0.0 %

           Branch data     Line data    Source code
       1                 :            : /* SPDX-License-Identifier: BSD-3-Clause
       2                 :            :  * Copyright(c) 2024 ZTE Corporation
       3                 :            :  */
       4                 :            : 
       5                 :            : #include <stdint.h>
       6                 :            : #include <rte_malloc.h>
       7                 :            : #include <rte_mbuf.h>
       8                 :            : 
       9                 :            : #include "zxdh_queue.h"
      10                 :            : #include "zxdh_logs.h"
      11                 :            : #include "zxdh_pci.h"
      12                 :            : #include "zxdh_common.h"
      13                 :            : #include "zxdh_msg.h"
      14                 :            : 
      15                 :            : #define ZXDH_MBUF_MIN_SIZE       sizeof(struct zxdh_net_hdr_dl)
      16                 :            : #define ZXDH_MBUF_SIZE_4K             4096
      17                 :            : #define ZXDH_RX_FREE_THRESH           32
      18                 :            : #define ZXDH_TX_FREE_THRESH           32
      19                 :            : 
      20                 :            : struct rte_mbuf *
      21                 :          0 : zxdh_queue_detach_unused(struct zxdh_virtqueue *vq)
      22                 :            : {
      23                 :            :         struct rte_mbuf *cookie = NULL;
      24                 :            :         int32_t          idx    = 0;
      25                 :            : 
      26         [ #  # ]:          0 :         if (vq == NULL)
      27                 :            :                 return NULL;
      28                 :            : 
      29         [ #  # ]:          0 :         for (idx = 0; idx < vq->vq_nentries; idx++) {
      30                 :          0 :                 cookie = vq->vq_descx[idx].cookie;
      31         [ #  # ]:          0 :                 if (cookie != NULL) {
      32                 :          0 :                         vq->vq_descx[idx].cookie = NULL;
      33                 :          0 :                         return cookie;
      34                 :            :                 }
      35                 :            :         }
      36                 :            :         return NULL;
      37                 :            : }
      38                 :            : 
      39                 :            : static void
      40                 :          0 : zxdh_clear_channel(struct rte_eth_dev *dev, uint16_t lch)
      41                 :            : {
      42                 :          0 :         struct zxdh_hw *hw = dev->data->dev_private;
      43                 :            :         uint16_t pch;
      44                 :            :         uint32_t var, addr, widx, bidx;
      45                 :            : 
      46         [ #  # ]:          0 :         if (hw->channel_context[lch].valid == 0)
      47                 :            :                 return;
      48                 :            :         /* get coi table offset and index */
      49                 :          0 :         pch  = hw->channel_context[lch].ph_chno;
      50                 :          0 :         widx = pch / 32;
      51                 :          0 :         bidx = pch % 32;
      52                 :          0 :         addr = ZXDH_QUERES_SHARE_BASE + (widx * sizeof(uint32_t));
      53                 :          0 :         var  = zxdh_read_bar_reg(dev, ZXDH_BAR0_INDEX, addr);
      54                 :          0 :         var &= ~(1 << bidx);
      55                 :          0 :         zxdh_write_bar_reg(dev, ZXDH_BAR0_INDEX, addr, var);
      56                 :          0 :         hw->channel_context[lch].valid = 0;
      57                 :          0 :         hw->channel_context[lch].ph_chno = 0;
      58                 :          0 :         PMD_DRV_LOG(DEBUG, " phyque %d release end ", pch);
      59                 :            : }
      60                 :            : 
      61                 :            : static int32_t
      62                 :          0 : zxdh_release_channel(struct rte_eth_dev *dev)
      63                 :            : {
      64                 :          0 :         struct zxdh_hw *hw = dev->data->dev_private;
      65                 :          0 :         u_int16_t rxq_num = hw->rx_qnum;
      66                 :          0 :         u_int16_t txq_num = hw->tx_qnum;
      67                 :            :         uint16_t lch, i;
      68                 :            :         int32_t ret = 0;
      69                 :            : 
      70         [ #  # ]:          0 :         if (hw->queue_set_flag == 1) {
      71         [ #  # ]:          0 :                 for (i = 0; i < rxq_num; i++) {
      72                 :          0 :                         lch = i * 2;
      73                 :          0 :                         PMD_DRV_LOG(DEBUG, "free success!");
      74         [ #  # ]:          0 :                         if (hw->channel_context[lch].valid == 0)
      75                 :          0 :                                 continue;
      76                 :          0 :                         PMD_DRV_LOG(DEBUG, "phyque %d  no need to release backend do it",
      77                 :            :                                                 hw->channel_context[lch].ph_chno);
      78                 :          0 :                         hw->channel_context[lch].valid = 0;
      79                 :          0 :                         hw->channel_context[lch].ph_chno = 0;
      80                 :            :                 }
      81         [ #  # ]:          0 :                 for (i = 0; i < txq_num; i++) {
      82                 :          0 :                         lch = i * 2 + 1;
      83                 :          0 :                         PMD_DRV_LOG(DEBUG, "free success!");
      84         [ #  # ]:          0 :                         if (hw->channel_context[lch].valid == 0)
      85                 :          0 :                                 continue;
      86                 :          0 :                         PMD_DRV_LOG(DEBUG, "phyque %d  no need to release backend do it",
      87                 :            :                                                 hw->channel_context[lch].ph_chno);
      88                 :          0 :                         hw->channel_context[lch].valid = 0;
      89                 :          0 :                         hw->channel_context[lch].ph_chno = 0;
      90                 :            :                 }
      91                 :          0 :                 hw->queue_set_flag = 0;
      92                 :          0 :                 return 0;
      93                 :            :         }
      94                 :          0 :         ret = zxdh_timedlock(hw, 1000);
      95         [ #  # ]:          0 :         if (ret) {
      96                 :          0 :                 PMD_DRV_LOG(ERR, "Acquiring hw lock got failed, timeout");
      97                 :          0 :                 return -1;
      98                 :            :         }
      99                 :            : 
     100         [ #  # ]:          0 :         for (i = 0 ; i < rxq_num ; i++) {
     101                 :          0 :                 lch = i * 2;
     102                 :          0 :                 zxdh_clear_channel(dev, lch);
     103                 :            :         }
     104         [ #  # ]:          0 :         for (i = 0; i < txq_num ; i++) {
     105                 :          0 :                 lch = i * 2 + 1;
     106                 :          0 :                 zxdh_clear_channel(dev, lch);
     107                 :            :         }
     108                 :          0 :         zxdh_release_lock(hw);
     109                 :            : 
     110                 :          0 :         return 0;
     111                 :            : }
     112                 :            : 
     113                 :            : int32_t
     114                 :          0 : zxdh_get_queue_type(uint16_t vtpci_queue_idx)
     115                 :            : {
     116         [ #  # ]:          0 :         if (vtpci_queue_idx % 2 == 0)
     117                 :            :                 return ZXDH_VTNET_RQ;
     118                 :            :         else
     119                 :          0 :                 return ZXDH_VTNET_TQ;
     120                 :            : }
     121                 :            : 
     122                 :            : int32_t
     123                 :          0 : zxdh_free_queues(struct rte_eth_dev *dev)
     124                 :            : {
     125                 :          0 :         struct zxdh_hw *hw = dev->data->dev_private;
     126                 :            :         struct zxdh_virtqueue *vq = NULL;
     127                 :          0 :         u_int16_t rxq_num = hw->rx_qnum;
     128                 :          0 :         u_int16_t txq_num = hw->tx_qnum;
     129                 :            :         uint16_t i = 0;
     130                 :            : 
     131         [ #  # ]:          0 :         if (hw->vqs == NULL)
     132                 :            :                 return 0;
     133                 :            : 
     134         [ #  # ]:          0 :         for (i = 0; i < rxq_num; i++) {
     135                 :          0 :                 vq = hw->vqs[i * 2];
     136         [ #  # ]:          0 :                 if (vq == NULL)
     137                 :          0 :                         continue;
     138                 :            : 
     139                 :          0 :                 ZXDH_VTPCI_OPS(hw)->del_queue(hw, vq);
     140                 :          0 :                 rte_memzone_free(vq->rxq.mz);
     141                 :          0 :                 rte_free(vq);
     142                 :          0 :                 hw->vqs[i * 2] = NULL;
     143                 :          0 :                 PMD_MSG_LOG(DEBUG, "Release to queue %d success!", i * 2);
     144                 :            :         }
     145         [ #  # ]:          0 :         for (i = 0; i < txq_num; i++) {
     146                 :          0 :                 vq = hw->vqs[i * 2 + 1];
     147         [ #  # ]:          0 :                 if (vq == NULL)
     148                 :          0 :                         continue;
     149                 :            : 
     150                 :          0 :                 ZXDH_VTPCI_OPS(hw)->del_queue(hw, vq);
     151                 :          0 :                 rte_memzone_free(vq->txq.mz);
     152                 :          0 :                 rte_memzone_free(vq->txq.zxdh_net_hdr_mz);
     153                 :          0 :                 rte_free(vq);
     154                 :          0 :                 hw->vqs[i * 2 + 1] = NULL;
     155                 :          0 :                 PMD_DRV_LOG(DEBUG, "Release to queue %d success!", i * 2 + 1);
     156                 :            :         }
     157                 :            : 
     158         [ #  # ]:          0 :         if (zxdh_release_channel(dev) < 0) {
     159                 :          0 :                 PMD_DRV_LOG(ERR, "Failed to clear coi table");
     160                 :          0 :                 return -1;
     161                 :            :         }
     162                 :            : 
     163                 :          0 :         rte_free(hw->vqs);
     164                 :          0 :         hw->vqs = NULL;
     165                 :            : 
     166                 :          0 :         return 0;
     167                 :            : }
     168                 :            : 
     169                 :            : static int
     170                 :          0 : zxdh_check_mempool(struct rte_mempool *mp, uint16_t offset, uint16_t min_length)
     171                 :            : {
     172                 :            :         uint16_t data_room_size;
     173                 :            : 
     174         [ #  # ]:          0 :         if (mp == NULL)
     175                 :            :                 return -EINVAL;
     176                 :            :         data_room_size = rte_pktmbuf_data_room_size(mp);
     177         [ #  # ]:          0 :         if (data_room_size < offset + min_length) {
     178                 :          0 :                 PMD_RX_LOG(ERR,
     179                 :            :                                    "%s mbuf_data_room_size %u < %u (%u + %u)",
     180                 :            :                                    mp->name, data_room_size,
     181                 :            :                                    offset + min_length, offset, min_length);
     182                 :          0 :                 return -EINVAL;
     183                 :            :         }
     184                 :            :         return 0;
     185                 :            : }
     186                 :            : 
     187                 :            : int32_t
     188                 :          0 : zxdh_dev_rx_queue_setup(struct rte_eth_dev *dev,
     189                 :            :                         uint16_t queue_idx,
     190                 :            :                         uint16_t nb_desc,
     191                 :            :                         uint32_t socket_id __rte_unused,
     192                 :            :                         const struct rte_eth_rxconf *rx_conf,
     193                 :            :                         struct rte_mempool *mp)
     194                 :            : {
     195                 :          0 :         struct zxdh_hw *hw = dev->data->dev_private;
     196                 :          0 :         uint16_t vtpci_logic_qidx = 2 * queue_idx + ZXDH_RQ_QUEUE_IDX;
     197                 :          0 :         struct zxdh_virtqueue *vq = hw->vqs[vtpci_logic_qidx];
     198                 :            :         int32_t ret = 0;
     199                 :            : 
     200         [ #  # ]:          0 :         if (rx_conf->rx_deferred_start) {
     201                 :          0 :                 PMD_RX_LOG(ERR, "Rx deferred start is not supported");
     202                 :          0 :                 return -EINVAL;
     203                 :            :         }
     204                 :          0 :         uint16_t rx_free_thresh = rx_conf->rx_free_thresh;
     205                 :            : 
     206         [ #  # ]:          0 :         if (rx_free_thresh == 0)
     207                 :          0 :                 rx_free_thresh = RTE_MIN(vq->vq_nentries / 4, ZXDH_RX_FREE_THRESH);
     208                 :            : 
     209                 :            :         /* rx_free_thresh must be multiples of four. */
     210         [ #  # ]:          0 :         if (rx_free_thresh & 0x3) {
     211                 :          0 :                 PMD_RX_LOG(ERR, "(rx_free_thresh=%u port=%u queue=%u)",
     212                 :            :                         rx_free_thresh, dev->data->port_id, queue_idx);
     213                 :          0 :                 return -EINVAL;
     214                 :            :         }
     215                 :            :         /* rx_free_thresh must be less than the number of RX entries */
     216         [ #  # ]:          0 :         if (rx_free_thresh >= vq->vq_nentries) {
     217                 :          0 :                 PMD_RX_LOG(ERR, "RX entries (%u). (rx_free_thresh=%u port=%u queue=%u)",
     218                 :            :                         vq->vq_nentries, rx_free_thresh, dev->data->port_id, queue_idx);
     219                 :          0 :                 return -EINVAL;
     220                 :            :         }
     221                 :          0 :         vq->vq_free_thresh = rx_free_thresh;
     222                 :            :         nb_desc = ZXDH_QUEUE_DEPTH;
     223                 :            : 
     224                 :          0 :         vq->vq_free_cnt = RTE_MIN(vq->vq_free_cnt, nb_desc);
     225                 :          0 :         struct zxdh_virtnet_rx *rxvq = &vq->rxq;
     226                 :            : 
     227                 :          0 :         rxvq->queue_id = vtpci_logic_qidx;
     228                 :            : 
     229                 :            :         int mbuf_min_size  = ZXDH_MBUF_MIN_SIZE;
     230                 :            : 
     231         [ #  # ]:          0 :         if (rx_conf->offloads & RTE_ETH_RX_OFFLOAD_TCP_LRO)
     232                 :            :                 mbuf_min_size = ZXDH_MBUF_SIZE_4K;
     233                 :            : 
     234                 :          0 :         ret = zxdh_check_mempool(mp, RTE_PKTMBUF_HEADROOM, mbuf_min_size);
     235         [ #  # ]:          0 :         if (ret != 0) {
     236                 :          0 :                 PMD_RX_LOG(ERR,
     237                 :            :                         "rxq setup but mpool size too small(<%d) failed", mbuf_min_size);
     238                 :          0 :                 return -EINVAL;
     239                 :            :         }
     240                 :          0 :         rxvq->mpool = mp;
     241         [ #  # ]:          0 :         if (queue_idx < dev->data->nb_rx_queues)
     242                 :          0 :                 dev->data->rx_queues[queue_idx] = rxvq;
     243                 :            : 
     244                 :            :         return 0;
     245                 :            : }
     246                 :            : 
     247                 :            : int32_t
     248                 :          0 : zxdh_dev_tx_queue_setup(struct rte_eth_dev *dev,
     249                 :            :                         uint16_t queue_idx,
     250                 :            :                         uint16_t nb_desc,
     251                 :            :                         uint32_t socket_id __rte_unused,
     252                 :            :                         const struct rte_eth_txconf *tx_conf)
     253                 :            : {
     254                 :          0 :         uint16_t vtpci_logic_qidx = 2 * queue_idx + ZXDH_TQ_QUEUE_IDX;
     255                 :          0 :         struct zxdh_hw *hw = dev->data->dev_private;
     256                 :          0 :         struct zxdh_virtqueue *vq = hw->vqs[vtpci_logic_qidx];
     257                 :            :         struct zxdh_virtnet_tx *txvq = NULL;
     258                 :            :         uint16_t tx_free_thresh = 0;
     259                 :            : 
     260         [ #  # ]:          0 :         if (tx_conf->tx_deferred_start) {
     261                 :          0 :                 PMD_TX_LOG(ERR, "Tx deferred start is not supported");
     262                 :          0 :                 return -EINVAL;
     263                 :            :         }
     264                 :            : 
     265                 :            :         nb_desc = ZXDH_QUEUE_DEPTH;
     266                 :            : 
     267                 :          0 :         vq->vq_free_cnt = RTE_MIN(vq->vq_free_cnt, nb_desc);
     268                 :            : 
     269                 :          0 :         txvq = &vq->txq;
     270                 :          0 :         txvq->queue_id = vtpci_logic_qidx;
     271                 :            : 
     272                 :          0 :         tx_free_thresh = tx_conf->tx_free_thresh;
     273         [ #  # ]:          0 :         if (tx_free_thresh == 0)
     274                 :          0 :                 tx_free_thresh = RTE_MIN(vq->vq_nentries / 4, ZXDH_TX_FREE_THRESH);
     275                 :            : 
     276                 :            :         /* tx_free_thresh must be less than the number of TX entries minus 3 */
     277         [ #  # ]:          0 :         if (tx_free_thresh >= (vq->vq_nentries - 3)) {
     278                 :          0 :                 PMD_TX_LOG(ERR, "TX entries - 3 (%u). (tx_free_thresh=%u port=%u queue=%u)",
     279                 :            :                                 vq->vq_nentries - 3, tx_free_thresh, dev->data->port_id, queue_idx);
     280                 :          0 :                 return -EINVAL;
     281                 :            :         }
     282                 :            : 
     283                 :          0 :         vq->vq_free_thresh = tx_free_thresh;
     284                 :            : 
     285         [ #  # ]:          0 :         if (queue_idx < dev->data->nb_tx_queues)
     286                 :          0 :                 dev->data->tx_queues[queue_idx] = txvq;
     287                 :            : 
     288                 :            :         return 0;
     289                 :            : }
     290                 :            : 
     291                 :            : int32_t
     292                 :          0 : zxdh_dev_rx_queue_intr_enable(struct rte_eth_dev *dev, uint16_t queue_id)
     293                 :            : {
     294                 :          0 :         struct zxdh_virtnet_rx *rxvq = dev->data->rx_queues[queue_id];
     295         [ #  # ]:          0 :         struct zxdh_virtqueue *vq = rxvq->vq;
     296                 :            : 
     297                 :            :         zxdh_queue_enable_intr(vq);
     298                 :          0 :         return 0;
     299                 :            : }
     300                 :            : 
     301                 :            : int32_t
     302                 :          0 : zxdh_dev_rx_queue_intr_disable(struct rte_eth_dev *dev, uint16_t queue_id)
     303                 :            : {
     304                 :          0 :         struct zxdh_virtnet_rx *rxvq = dev->data->rx_queues[queue_id];
     305         [ #  # ]:          0 :         struct zxdh_virtqueue  *vq      = rxvq->vq;
     306                 :            : 
     307                 :            :         zxdh_queue_disable_intr(vq);
     308                 :          0 :         return 0;
     309                 :            : }
     310                 :            : 
     311                 :          0 : int32_t zxdh_enqueue_recv_refill_packed(struct zxdh_virtqueue *vq,
     312                 :            :                         struct rte_mbuf **cookie, uint16_t num)
     313                 :            : {
     314                 :          0 :         struct zxdh_vring_packed_desc *start_dp = vq->vq_packed.ring.desc;
     315                 :            :         struct zxdh_vq_desc_extra *dxp;
     316                 :          0 :         uint16_t flags = vq->vq_packed.cached_flags;
     317                 :            :         int32_t i;
     318                 :            :         uint16_t idx;
     319                 :            : 
     320         [ #  # ]:          0 :         for (i = 0; i < num; i++) {
     321                 :          0 :                 idx = vq->vq_avail_idx;
     322                 :          0 :                 dxp = &vq->vq_descx[idx];
     323                 :          0 :                 dxp->cookie = (void *)cookie[i];
     324                 :          0 :                 dxp->ndescs = 1;
     325                 :            :                 /* rx pkt fill in data_off */
     326                 :          0 :                 start_dp[idx].addr = rte_mbuf_iova_get(cookie[i]) + RTE_PKTMBUF_HEADROOM;
     327                 :          0 :                 start_dp[idx].len = cookie[i]->buf_len - RTE_PKTMBUF_HEADROOM;
     328                 :            : 
     329                 :            :                 zxdh_queue_store_flags_packed(&start_dp[idx], flags);
     330         [ #  # ]:          0 :                 if (++vq->vq_avail_idx >= vq->vq_nentries) {
     331                 :          0 :                         vq->vq_avail_idx -= vq->vq_nentries;
     332                 :          0 :                         vq->vq_packed.cached_flags ^= ZXDH_VRING_PACKED_DESC_F_AVAIL_USED;
     333                 :            :                         flags = vq->vq_packed.cached_flags;
     334                 :            :                 }
     335                 :            :         }
     336                 :          0 :         vq->vq_free_cnt = (uint16_t)(vq->vq_free_cnt - num);
     337                 :          0 :         return 0;
     338                 :            : }
     339                 :            : 
     340                 :          0 : int32_t zxdh_dev_rx_queue_setup_finish(struct rte_eth_dev *dev, uint16_t logic_qidx)
     341                 :            : {
     342                 :          0 :         struct zxdh_hw *hw = dev->data->dev_private;
     343                 :          0 :         struct zxdh_virtqueue *vq = hw->vqs[logic_qidx];
     344                 :            :         struct zxdh_virtnet_rx *rxvq = &vq->rxq;
     345                 :            :         uint16_t desc_idx;
     346                 :            :         int32_t error = 0;
     347                 :            : 
     348                 :            :         /* Allocate blank mbufs for the each rx descriptor */
     349                 :          0 :         memset(&rxvq->fake_mbuf, 0, sizeof(rxvq->fake_mbuf));
     350         [ #  # ]:          0 :         for (desc_idx = 0; desc_idx < ZXDH_MBUF_BURST_SZ; desc_idx++)
     351                 :          0 :                 vq->sw_ring[vq->vq_nentries + desc_idx] = &rxvq->fake_mbuf;
     352                 :            : 
     353         [ #  # ]:          0 :         while (!zxdh_queue_full(vq)) {
     354                 :            :                 uint16_t free_cnt = vq->vq_free_cnt;
     355                 :            : 
     356                 :          0 :                 free_cnt = RTE_MIN(ZXDH_MBUF_BURST_SZ, free_cnt);
     357                 :          0 :                 struct rte_mbuf *new_pkts[free_cnt];
     358                 :            : 
     359         [ #  # ]:          0 :                 if (likely(rte_pktmbuf_alloc_bulk(rxvq->mpool, new_pkts, free_cnt) == 0)) {
     360                 :          0 :                         error = zxdh_enqueue_recv_refill_packed(vq, new_pkts, free_cnt);
     361         [ #  # ]:          0 :                         if (unlikely(error)) {
     362                 :            :                                 int32_t i;
     363         [ #  # ]:          0 :                                 for (i = 0; i < free_cnt; i++)
     364                 :          0 :                                         rte_pktmbuf_free(new_pkts[i]);
     365                 :            :                         }
     366                 :            :                 } else {
     367                 :          0 :                         PMD_DRV_LOG(ERR, "port %d rxq %d allocated bufs from %s failed",
     368                 :            :                                 hw->port_id, logic_qidx, rxvq->mpool->name);
     369                 :          0 :                         break;
     370                 :            :                 }
     371                 :            :         }
     372                 :          0 :         return 0;
     373                 :            : }
     374                 :            : 
     375                 :          0 : void zxdh_queue_rxvq_flush(struct zxdh_virtqueue *vq)
     376                 :            : {
     377                 :            :         struct zxdh_vq_desc_extra *dxp = NULL;
     378                 :            :         uint16_t i = 0;
     379                 :          0 :         struct zxdh_vring_packed_desc *descs = vq->vq_packed.ring.desc;
     380                 :            :         int32_t cnt = 0;
     381                 :            : 
     382                 :          0 :         i = vq->vq_used_cons_idx;
     383         [ #  # ]:          0 :         while (zxdh_desc_used(&descs[i], vq) && cnt++ < vq->vq_nentries) {
     384                 :          0 :                 dxp = &vq->vq_descx[descs[i].id];
     385         [ #  # ]:          0 :                 if (dxp->cookie != NULL) {
     386                 :          0 :                         rte_pktmbuf_free(dxp->cookie);
     387                 :          0 :                         dxp->cookie = NULL;
     388                 :            :                 }
     389                 :          0 :                 vq->vq_free_cnt++;
     390                 :          0 :                 vq->vq_used_cons_idx++;
     391         [ #  # ]:          0 :                 if (vq->vq_used_cons_idx >= vq->vq_nentries) {
     392                 :          0 :                         vq->vq_used_cons_idx -= vq->vq_nentries;
     393                 :          0 :                         vq->vq_packed.used_wrap_counter ^= 1;
     394                 :            :                 }
     395                 :          0 :                 i = vq->vq_used_cons_idx;
     396                 :            :         }
     397                 :          0 : }

Generated by: LCOV version 1.14