Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Vadym Kochan | 3523 | 98.99% | 2 | 40.00% |
Oleksandr Mazur | 18 | 0.51% | 1 | 20.00% |
Zhengchao Shao | 17 | 0.48% | 1 | 20.00% |
Julia Lawall | 1 | 0.03% | 1 | 20.00% |
Total | 3559 | 5 |
// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 /* Copyright (c) 2019-2020 Marvell International Ltd. All rights reserved */ #include <linux/bitfield.h> #include <linux/dmapool.h> #include <linux/etherdevice.h> #include <linux/if_vlan.h> #include <linux/platform_device.h> #include "prestera_dsa.h" #include "prestera.h" #include "prestera_hw.h" #include "prestera_rxtx.h" #include "prestera_devlink.h" #define PRESTERA_SDMA_WAIT_MUL 10 struct prestera_sdma_desc { __le32 word1; __le32 word2; __le32 buff; __le32 next; } __packed __aligned(16); #define PRESTERA_SDMA_BUFF_SIZE_MAX 1544 #define PRESTERA_SDMA_RX_DESC_PKT_LEN(desc) \ ((le32_to_cpu((desc)->word2) >> 16) & GENMASK(13, 0)) #define PRESTERA_SDMA_RX_DESC_OWNER(desc) \ ((le32_to_cpu((desc)->word1) & BIT(31)) >> 31) #define PRESTERA_SDMA_RX_DESC_IS_RCVD(desc) \ (PRESTERA_SDMA_RX_DESC_OWNER(desc) == PRESTERA_SDMA_RX_DESC_CPU_OWN) #define PRESTERA_SDMA_RX_DESC_CPU_OWN 0 #define PRESTERA_SDMA_RX_DESC_DMA_OWN 1 #define PRESTERA_SDMA_RX_QUEUE_NUM 8 #define PRESTERA_SDMA_RX_DESC_PER_Q 1000 #define PRESTERA_SDMA_TX_DESC_PER_Q 1000 #define PRESTERA_SDMA_TX_MAX_BURST 64 #define PRESTERA_SDMA_TX_DESC_OWNER(desc) \ ((le32_to_cpu((desc)->word1) & BIT(31)) >> 31) #define PRESTERA_SDMA_TX_DESC_CPU_OWN 0 #define PRESTERA_SDMA_TX_DESC_DMA_OWN 1U #define PRESTERA_SDMA_TX_DESC_IS_SENT(desc) \ (PRESTERA_SDMA_TX_DESC_OWNER(desc) == PRESTERA_SDMA_TX_DESC_CPU_OWN) #define PRESTERA_SDMA_TX_DESC_LAST BIT(20) #define PRESTERA_SDMA_TX_DESC_FIRST BIT(21) #define PRESTERA_SDMA_TX_DESC_CALC_CRC BIT(12) #define PRESTERA_SDMA_TX_DESC_SINGLE \ (PRESTERA_SDMA_TX_DESC_FIRST | PRESTERA_SDMA_TX_DESC_LAST) #define PRESTERA_SDMA_TX_DESC_INIT \ (PRESTERA_SDMA_TX_DESC_SINGLE | PRESTERA_SDMA_TX_DESC_CALC_CRC) #define PRESTERA_SDMA_RX_INTR_MASK_REG 0x2814 #define PRESTERA_SDMA_RX_QUEUE_STATUS_REG 0x2680 #define PRESTERA_SDMA_RX_QUEUE_DESC_REG(n) (0x260C + (n) * 16) #define PRESTERA_SDMA_TX_QUEUE_DESC_REG 0x26C0 #define PRESTERA_SDMA_TX_QUEUE_START_REG 0x2868 struct prestera_sdma_buf { struct prestera_sdma_desc *desc; dma_addr_t desc_dma; struct sk_buff *skb; dma_addr_t buf_dma; bool is_used; }; struct prestera_rx_ring { struct prestera_sdma_buf *bufs; int next_rx; }; struct prestera_tx_ring { struct prestera_sdma_buf *bufs; int next_tx; int max_burst; int burst; }; struct prestera_sdma { struct prestera_rx_ring rx_ring[PRESTERA_SDMA_RX_QUEUE_NUM]; struct prestera_tx_ring tx_ring; struct prestera_switch *sw; struct dma_pool *desc_pool; struct work_struct tx_work; struct napi_struct rx_napi; struct net_device napi_dev; u32 map_addr; u64 dma_mask; /* protect SDMA with concurrent access from multiple CPUs */ spinlock_t tx_lock; }; struct prestera_rxtx { struct prestera_sdma sdma; }; static int prestera_sdma_buf_init(struct prestera_sdma *sdma, struct prestera_sdma_buf *buf) { struct prestera_sdma_desc *desc; dma_addr_t dma; desc = dma_pool_alloc(sdma->desc_pool, GFP_DMA | GFP_KERNEL, &dma); if (!desc) return -ENOMEM; buf->buf_dma = DMA_MAPPING_ERROR; buf->desc_dma = dma; buf->desc = desc; buf->skb = NULL; return 0; } static u32 prestera_sdma_map(struct prestera_sdma *sdma, dma_addr_t pa) { return sdma->map_addr + pa; } static void prestera_sdma_rx_desc_init(struct prestera_sdma *sdma, struct prestera_sdma_desc *desc, dma_addr_t buf) { u32 word = le32_to_cpu(desc->word2); u32p_replace_bits(&word, PRESTERA_SDMA_BUFF_SIZE_MAX, GENMASK(15, 0)); desc->word2 = cpu_to_le32(word); desc->buff = cpu_to_le32(prestera_sdma_map(sdma, buf)); /* make sure buffer is set before reset the descriptor */ wmb(); desc->word1 = cpu_to_le32(0xA0000000); } static void prestera_sdma_rx_desc_set_next(struct prestera_sdma *sdma, struct prestera_sdma_desc *desc, dma_addr_t next) { desc->next = cpu_to_le32(prestera_sdma_map(sdma, next)); } static int prestera_sdma_rx_skb_alloc(struct prestera_sdma *sdma, struct prestera_sdma_buf *buf) { struct device *dev = sdma->sw->dev->dev; struct sk_buff *skb; dma_addr_t dma; skb = alloc_skb(PRESTERA_SDMA_BUFF_SIZE_MAX, GFP_DMA | GFP_ATOMIC); if (!skb) return -ENOMEM; dma = dma_map_single(dev, skb->data, skb->len, DMA_FROM_DEVICE); if (dma_mapping_error(dev, dma)) goto err_dma_map; if (buf->skb) dma_unmap_single(dev, buf->buf_dma, buf->skb->len, DMA_FROM_DEVICE); buf->buf_dma = dma; buf->skb = skb; return 0; err_dma_map: kfree_skb(skb); return -ENOMEM; } static struct sk_buff *prestera_sdma_rx_skb_get(struct prestera_sdma *sdma, struct prestera_sdma_buf *buf) { dma_addr_t buf_dma = buf->buf_dma; struct sk_buff *skb = buf->skb; u32 len = skb->len; int err; err = prestera_sdma_rx_skb_alloc(sdma, buf); if (err) { buf->buf_dma = buf_dma; buf->skb = skb; skb = alloc_skb(skb->len, GFP_ATOMIC); if (skb) { skb_put(skb, len); skb_copy_from_linear_data(buf->skb, skb->data, len); } } prestera_sdma_rx_desc_init(sdma, buf->desc, buf->buf_dma); return skb; } static int prestera_rxtx_process_skb(struct prestera_sdma *sdma, struct sk_buff *skb) { struct prestera_port *port; struct prestera_dsa dsa; u32 hw_port, dev_id; u8 cpu_code; int err; skb_pull(skb, ETH_HLEN); /* ethertype field is part of the dsa header */ err = prestera_dsa_parse(&dsa, skb->data - ETH_TLEN); if (err) return err; dev_id = dsa.hw_dev_num; hw_port = dsa.port_num; port = prestera_port_find_by_hwid(sdma->sw, dev_id, hw_port); if (unlikely(!port)) { dev_warn_ratelimited(prestera_dev(sdma->sw), "received pkt for non-existent port(%u, %u)\n", dev_id, hw_port); return -ENOENT; } if (unlikely(!pskb_may_pull(skb, PRESTERA_DSA_HLEN))) return -EINVAL; /* remove DSA tag and update checksum */ skb_pull_rcsum(skb, PRESTERA_DSA_HLEN); memmove(skb->data - ETH_HLEN, skb->data - ETH_HLEN - PRESTERA_DSA_HLEN, ETH_ALEN * 2); skb_push(skb, ETH_HLEN); skb->protocol = eth_type_trans(skb, port->dev); if (dsa.vlan.is_tagged) { u16 tci = dsa.vlan.vid & VLAN_VID_MASK; tci |= dsa.vlan.vpt << VLAN_PRIO_SHIFT; if (dsa.vlan.cfi_bit) tci |= VLAN_CFI_MASK; __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), tci); } cpu_code = dsa.cpu_code; prestera_devlink_trap_report(port, skb, cpu_code); return 0; } static int prestera_sdma_next_rx_buf_idx(int buf_idx) { return (buf_idx + 1) % PRESTERA_SDMA_RX_DESC_PER_Q; } static int prestera_sdma_rx_poll(struct napi_struct *napi, int budget) { int qnum = PRESTERA_SDMA_RX_QUEUE_NUM; unsigned int rxq_done_map = 0; struct prestera_sdma *sdma; struct list_head rx_list; unsigned int qmask; int pkts_done = 0; int q; qnum = PRESTERA_SDMA_RX_QUEUE_NUM; qmask = GENMASK(qnum - 1, 0); INIT_LIST_HEAD(&rx_list); sdma = container_of(napi, struct prestera_sdma, rx_napi); while (pkts_done < budget && rxq_done_map != qmask) { for (q = 0; q < qnum && pkts_done < budget; q++) { struct prestera_rx_ring *ring = &sdma->rx_ring[q]; struct prestera_sdma_desc *desc; struct prestera_sdma_buf *buf; int buf_idx = ring->next_rx; struct sk_buff *skb; buf = &ring->bufs[buf_idx]; desc = buf->desc; if (PRESTERA_SDMA_RX_DESC_IS_RCVD(desc)) { rxq_done_map &= ~BIT(q); } else { rxq_done_map |= BIT(q); continue; } pkts_done++; __skb_trim(buf->skb, PRESTERA_SDMA_RX_DESC_PKT_LEN(desc)); skb = prestera_sdma_rx_skb_get(sdma, buf); if (!skb) goto rx_next_buf; if (unlikely(prestera_rxtx_process_skb(sdma, skb))) goto rx_next_buf; list_add_tail(&skb->list, &rx_list); rx_next_buf: ring->next_rx = prestera_sdma_next_rx_buf_idx(buf_idx); } } if (pkts_done < budget && napi_complete_done(napi, pkts_done)) prestera_write(sdma->sw, PRESTERA_SDMA_RX_INTR_MASK_REG, GENMASK(9, 2)); netif_receive_skb_list(&rx_list); return pkts_done; } static void prestera_sdma_rx_fini(struct prestera_sdma *sdma) { int qnum = PRESTERA_SDMA_RX_QUEUE_NUM; int q, b; /* disable all rx queues */ prestera_write(sdma->sw, PRESTERA_SDMA_RX_QUEUE_STATUS_REG, GENMASK(15, 8)); for (q = 0; q < qnum; q++) { struct prestera_rx_ring *ring = &sdma->rx_ring[q]; if (!ring->bufs) break; for (b = 0; b < PRESTERA_SDMA_RX_DESC_PER_Q; b++) { struct prestera_sdma_buf *buf = &ring->bufs[b]; if (buf->desc_dma) dma_pool_free(sdma->desc_pool, buf->desc, buf->desc_dma); if (!buf->skb) continue; if (buf->buf_dma != DMA_MAPPING_ERROR) dma_unmap_single(sdma->sw->dev->dev, buf->buf_dma, buf->skb->len, DMA_FROM_DEVICE); kfree_skb(buf->skb); } } } static int prestera_sdma_rx_init(struct prestera_sdma *sdma) { int bnum = PRESTERA_SDMA_RX_DESC_PER_Q; int qnum = PRESTERA_SDMA_RX_QUEUE_NUM; int err; int q; /* disable all rx queues */ prestera_write(sdma->sw, PRESTERA_SDMA_RX_QUEUE_STATUS_REG, GENMASK(15, 8)); for (q = 0; q < qnum; q++) { struct prestera_sdma_buf *head, *tail, *next, *prev; struct prestera_rx_ring *ring = &sdma->rx_ring[q]; ring->bufs = kmalloc_array(bnum, sizeof(*head), GFP_KERNEL); if (!ring->bufs) return -ENOMEM; ring->next_rx = 0; tail = &ring->bufs[bnum - 1]; head = &ring->bufs[0]; next = head; prev = next; do { err = prestera_sdma_buf_init(sdma, next); if (err) return err; err = prestera_sdma_rx_skb_alloc(sdma, next); if (err) return err; prestera_sdma_rx_desc_init(sdma, next->desc, next->buf_dma); prestera_sdma_rx_desc_set_next(sdma, prev->desc, next->desc_dma); prev = next; next++; } while (prev != tail); /* join tail with head to make a circular list */ prestera_sdma_rx_desc_set_next(sdma, tail->desc, head->desc_dma); prestera_write(sdma->sw, PRESTERA_SDMA_RX_QUEUE_DESC_REG(q), prestera_sdma_map(sdma, head->desc_dma)); } /* make sure all rx descs are filled before enabling all rx queues */ wmb(); prestera_write(sdma->sw, PRESTERA_SDMA_RX_QUEUE_STATUS_REG, GENMASK(7, 0)); return 0; } static void prestera_sdma_tx_desc_init(struct prestera_sdma *sdma, struct prestera_sdma_desc *desc) { desc->word1 = cpu_to_le32(PRESTERA_SDMA_TX_DESC_INIT); desc->word2 = 0; } static void prestera_sdma_tx_desc_set_next(struct prestera_sdma *sdma, struct prestera_sdma_desc *desc, dma_addr_t next) { desc->next = cpu_to_le32(prestera_sdma_map(sdma, next)); } static void prestera_sdma_tx_desc_set_buf(struct prestera_sdma *sdma, struct prestera_sdma_desc *desc, dma_addr_t buf, size_t len) { u32 word = le32_to_cpu(desc->word2); u32p_replace_bits(&word, len + ETH_FCS_LEN, GENMASK(30, 16)); desc->buff = cpu_to_le32(prestera_sdma_map(sdma, buf)); desc->word2 = cpu_to_le32(word); } static void prestera_sdma_tx_desc_xmit(struct prestera_sdma_desc *desc) { u32 word = le32_to_cpu(desc->word1); word |= PRESTERA_SDMA_TX_DESC_DMA_OWN << 31; /* make sure everything is written before enable xmit */ wmb(); desc->word1 = cpu_to_le32(word); } static int prestera_sdma_tx_buf_map(struct prestera_sdma *sdma, struct prestera_sdma_buf *buf, struct sk_buff *skb) { struct device *dma_dev = sdma->sw->dev->dev; dma_addr_t dma; dma = dma_map_single(dma_dev, skb->data, skb->len, DMA_TO_DEVICE); if (dma_mapping_error(dma_dev, dma)) return -ENOMEM; buf->buf_dma = dma; buf->skb = skb; return 0; } static void prestera_sdma_tx_buf_unmap(struct prestera_sdma *sdma, struct prestera_sdma_buf *buf) { struct device *dma_dev = sdma->sw->dev->dev; dma_unmap_single(dma_dev, buf->buf_dma, buf->skb->len, DMA_TO_DEVICE); } static void prestera_sdma_tx_recycle_work_fn(struct work_struct *work) { int bnum = PRESTERA_SDMA_TX_DESC_PER_Q; struct prestera_tx_ring *tx_ring; struct prestera_sdma *sdma; int b; sdma = container_of(work, struct prestera_sdma, tx_work); tx_ring = &sdma->tx_ring; for (b = 0; b < bnum; b++) { struct prestera_sdma_buf *buf = &tx_ring->bufs[b]; if (!buf->is_used) continue; if (!PRESTERA_SDMA_TX_DESC_IS_SENT(buf->desc)) continue; prestera_sdma_tx_buf_unmap(sdma, buf); dev_consume_skb_any(buf->skb); buf->skb = NULL; /* make sure everything is cleaned up */ wmb(); buf->is_used = false; } } static int prestera_sdma_tx_init(struct prestera_sdma *sdma) { struct prestera_sdma_buf *head, *tail, *next, *prev; struct prestera_tx_ring *tx_ring = &sdma->tx_ring; int bnum = PRESTERA_SDMA_TX_DESC_PER_Q; int err; INIT_WORK(&sdma->tx_work, prestera_sdma_tx_recycle_work_fn); spin_lock_init(&sdma->tx_lock); tx_ring->bufs = kmalloc_array(bnum, sizeof(*head), GFP_KERNEL); if (!tx_ring->bufs) return -ENOMEM; tail = &tx_ring->bufs[bnum - 1]; head = &tx_ring->bufs[0]; next = head; prev = next; tx_ring->max_burst = PRESTERA_SDMA_TX_MAX_BURST; tx_ring->burst = tx_ring->max_burst; tx_ring->next_tx = 0; do { err = prestera_sdma_buf_init(sdma, next); if (err) return err; next->is_used = false; prestera_sdma_tx_desc_init(sdma, next->desc); prestera_sdma_tx_desc_set_next(sdma, prev->desc, next->desc_dma); prev = next; next++; } while (prev != tail); /* join tail with head to make a circular list */ prestera_sdma_tx_desc_set_next(sdma, tail->desc, head->desc_dma); /* make sure descriptors are written */ wmb(); prestera_write(sdma->sw, PRESTERA_SDMA_TX_QUEUE_DESC_REG, prestera_sdma_map(sdma, head->desc_dma)); return 0; } static void prestera_sdma_tx_fini(struct prestera_sdma *sdma) { struct prestera_tx_ring *ring = &sdma->tx_ring; int bnum = PRESTERA_SDMA_TX_DESC_PER_Q; int b; cancel_work_sync(&sdma->tx_work); if (!ring->bufs) return; for (b = 0; b < bnum; b++) { struct prestera_sdma_buf *buf = &ring->bufs[b]; if (buf->desc) dma_pool_free(sdma->desc_pool, buf->desc, buf->desc_dma); if (!buf->skb) continue; dma_unmap_single(sdma->sw->dev->dev, buf->buf_dma, buf->skb->len, DMA_TO_DEVICE); dev_consume_skb_any(buf->skb); } } static void prestera_rxtx_handle_event(struct prestera_switch *sw, struct prestera_event *evt, void *arg) { struct prestera_sdma *sdma = arg; if (evt->id != PRESTERA_RXTX_EVENT_RCV_PKT) return; prestera_write(sdma->sw, PRESTERA_SDMA_RX_INTR_MASK_REG, 0); napi_schedule(&sdma->rx_napi); } static int prestera_sdma_switch_init(struct prestera_switch *sw) { struct prestera_sdma *sdma = &sw->rxtx->sdma; struct device *dev = sw->dev->dev; struct prestera_rxtx_params p; int err; p.use_sdma = true; err = prestera_hw_rxtx_init(sw, &p); if (err) { dev_err(dev, "failed to init rxtx by hw\n"); return err; } sdma->dma_mask = dma_get_mask(dev); sdma->map_addr = p.map_addr; sdma->sw = sw; sdma->desc_pool = dma_pool_create("desc_pool", dev, sizeof(struct prestera_sdma_desc), 16, 0); if (!sdma->desc_pool) return -ENOMEM; err = prestera_sdma_rx_init(sdma); if (err) { dev_err(dev, "failed to init rx ring\n"); goto err_rx_init; } err = prestera_sdma_tx_init(sdma); if (err) { dev_err(dev, "failed to init tx ring\n"); goto err_tx_init; } err = prestera_hw_event_handler_register(sw, PRESTERA_EVENT_TYPE_RXTX, prestera_rxtx_handle_event, sdma); if (err) goto err_evt_register; init_dummy_netdev(&sdma->napi_dev); netif_napi_add(&sdma->napi_dev, &sdma->rx_napi, prestera_sdma_rx_poll); napi_enable(&sdma->rx_napi); return 0; err_evt_register: err_tx_init: prestera_sdma_tx_fini(sdma); err_rx_init: prestera_sdma_rx_fini(sdma); dma_pool_destroy(sdma->desc_pool); return err; } static void prestera_sdma_switch_fini(struct prestera_switch *sw) { struct prestera_sdma *sdma = &sw->rxtx->sdma; napi_disable(&sdma->rx_napi); netif_napi_del(&sdma->rx_napi); prestera_hw_event_handler_unregister(sw, PRESTERA_EVENT_TYPE_RXTX, prestera_rxtx_handle_event); prestera_sdma_tx_fini(sdma); prestera_sdma_rx_fini(sdma); dma_pool_destroy(sdma->desc_pool); } static bool prestera_sdma_is_ready(struct prestera_sdma *sdma) { return !(prestera_read(sdma->sw, PRESTERA_SDMA_TX_QUEUE_START_REG) & 1); } static int prestera_sdma_tx_wait(struct prestera_sdma *sdma, struct prestera_tx_ring *tx_ring) { int tx_wait_num = PRESTERA_SDMA_WAIT_MUL * tx_ring->max_burst; do { if (prestera_sdma_is_ready(sdma)) return 0; udelay(1); } while (--tx_wait_num); return -EBUSY; } static void prestera_sdma_tx_start(struct prestera_sdma *sdma) { prestera_write(sdma->sw, PRESTERA_SDMA_TX_QUEUE_START_REG, 1); schedule_work(&sdma->tx_work); } static netdev_tx_t prestera_sdma_xmit(struct prestera_sdma *sdma, struct sk_buff *skb) { struct device *dma_dev = sdma->sw->dev->dev; struct net_device *dev = skb->dev; struct prestera_tx_ring *tx_ring; struct prestera_sdma_buf *buf; int err; spin_lock(&sdma->tx_lock); tx_ring = &sdma->tx_ring; buf = &tx_ring->bufs[tx_ring->next_tx]; if (buf->is_used) { schedule_work(&sdma->tx_work); goto drop_skb; } if (unlikely(eth_skb_pad(skb))) goto drop_skb_nofree; err = prestera_sdma_tx_buf_map(sdma, buf, skb); if (err) goto drop_skb; prestera_sdma_tx_desc_set_buf(sdma, buf->desc, buf->buf_dma, skb->len); dma_sync_single_for_device(dma_dev, buf->buf_dma, skb->len, DMA_TO_DEVICE); if (tx_ring->burst) { tx_ring->burst--; } else { tx_ring->burst = tx_ring->max_burst; err = prestera_sdma_tx_wait(sdma, tx_ring); if (err) goto drop_skb_unmap; } tx_ring->next_tx = (tx_ring->next_tx + 1) % PRESTERA_SDMA_TX_DESC_PER_Q; prestera_sdma_tx_desc_xmit(buf->desc); buf->is_used = true; prestera_sdma_tx_start(sdma); goto tx_done; drop_skb_unmap: prestera_sdma_tx_buf_unmap(sdma, buf); drop_skb: dev_consume_skb_any(skb); drop_skb_nofree: dev->stats.tx_dropped++; tx_done: spin_unlock(&sdma->tx_lock); return NETDEV_TX_OK; } int prestera_rxtx_switch_init(struct prestera_switch *sw) { struct prestera_rxtx *rxtx; int err; rxtx = kzalloc(sizeof(*rxtx), GFP_KERNEL); if (!rxtx) return -ENOMEM; sw->rxtx = rxtx; err = prestera_sdma_switch_init(sw); if (err) kfree(rxtx); return err; } void prestera_rxtx_switch_fini(struct prestera_switch *sw) { prestera_sdma_switch_fini(sw); kfree(sw->rxtx); } int prestera_rxtx_port_init(struct prestera_port *port) { port->dev->needed_headroom = PRESTERA_DSA_HLEN; return 0; } netdev_tx_t prestera_rxtx_xmit(struct prestera_port *port, struct sk_buff *skb) { struct prestera_dsa dsa; dsa.hw_dev_num = port->dev_id; dsa.port_num = port->hw_id; if (skb_cow_head(skb, PRESTERA_DSA_HLEN) < 0) return NET_XMIT_DROP; skb_push(skb, PRESTERA_DSA_HLEN); memmove(skb->data, skb->data + PRESTERA_DSA_HLEN, 2 * ETH_ALEN); if (prestera_dsa_build(&dsa, skb->data + 2 * ETH_ALEN) != 0) return NET_XMIT_DROP; return prestera_sdma_xmit(&port->sw->rxtx->sdma, skb); }
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1