Tomas Winkler | ce0925e | 2018-11-22 13:11:36 +0200 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | /* |
| 3 | * Copyright(c) 2016 - 2018 Intel Corporation. All rights reserved. |
| 4 | */ |
| 5 | #include <linux/dma-mapping.h> |
| 6 | #include <linux/mei.h> |
| 7 | |
| 8 | #include "mei_dev.h" |
| 9 | |
| 10 | /** |
| 11 | * mei_dmam_dscr_alloc() - allocate a managed coherent buffer |
| 12 | * for the dma descriptor |
| 13 | * @dev: mei_device |
| 14 | * @dscr: dma descriptor |
| 15 | * |
| 16 | * Return: |
| 17 | * * 0 - on success or zero allocation request |
| 18 | * * -EINVAL - if size is not power of 2 |
| 19 | * * -ENOMEM - of allocation has failed |
| 20 | */ |
| 21 | static int mei_dmam_dscr_alloc(struct mei_device *dev, |
| 22 | struct mei_dma_dscr *dscr) |
| 23 | { |
| 24 | if (!dscr->size) |
| 25 | return 0; |
| 26 | |
| 27 | if (WARN_ON(!is_power_of_2(dscr->size))) |
| 28 | return -EINVAL; |
| 29 | |
| 30 | if (dscr->vaddr) |
| 31 | return 0; |
| 32 | |
| 33 | dscr->vaddr = dmam_alloc_coherent(dev->dev, dscr->size, &dscr->daddr, |
| 34 | GFP_KERNEL); |
| 35 | if (!dscr->vaddr) |
| 36 | return -ENOMEM; |
| 37 | |
| 38 | return 0; |
| 39 | } |
| 40 | |
| 41 | /** |
| 42 | * mei_dmam_dscr_free() - free a managed coherent buffer |
| 43 | * from the dma descriptor |
| 44 | * @dev: mei_device |
| 45 | * @dscr: dma descriptor |
| 46 | */ |
| 47 | static void mei_dmam_dscr_free(struct mei_device *dev, |
| 48 | struct mei_dma_dscr *dscr) |
| 49 | { |
| 50 | if (!dscr->vaddr) |
| 51 | return; |
| 52 | |
| 53 | dmam_free_coherent(dev->dev, dscr->size, dscr->vaddr, dscr->daddr); |
| 54 | dscr->vaddr = NULL; |
| 55 | } |
| 56 | |
| 57 | /** |
| 58 | * mei_dmam_ring_free() - free dma ring buffers |
| 59 | * @dev: mei device |
| 60 | */ |
| 61 | void mei_dmam_ring_free(struct mei_device *dev) |
| 62 | { |
| 63 | int i; |
| 64 | |
| 65 | for (i = 0; i < DMA_DSCR_NUM; i++) |
| 66 | mei_dmam_dscr_free(dev, &dev->dr_dscr[i]); |
| 67 | } |
| 68 | |
| 69 | /** |
| 70 | * mei_dmam_ring_alloc() - allocate dma ring buffers |
| 71 | * @dev: mei device |
| 72 | * |
| 73 | * Return: -ENOMEM on allocation failure 0 otherwise |
| 74 | */ |
| 75 | int mei_dmam_ring_alloc(struct mei_device *dev) |
| 76 | { |
| 77 | int i; |
| 78 | |
| 79 | for (i = 0; i < DMA_DSCR_NUM; i++) |
| 80 | if (mei_dmam_dscr_alloc(dev, &dev->dr_dscr[i])) |
| 81 | goto err; |
| 82 | |
| 83 | return 0; |
| 84 | |
| 85 | err: |
| 86 | mei_dmam_ring_free(dev); |
| 87 | return -ENOMEM; |
| 88 | } |
| 89 | |
| 90 | /** |
| 91 | * mei_dma_ring_is_allocated() - check if dma ring is allocated |
| 92 | * @dev: mei device |
| 93 | * |
| 94 | * Return: true if dma ring is allocated |
| 95 | */ |
| 96 | bool mei_dma_ring_is_allocated(struct mei_device *dev) |
| 97 | { |
| 98 | return !!dev->dr_dscr[DMA_DSCR_HOST].vaddr; |
| 99 | } |
Tomas Winkler | 2513eb0 | 2018-11-22 13:11:38 +0200 | [diff] [blame] | 100 | |
| 101 | static inline |
| 102 | struct hbm_dma_ring_ctrl *mei_dma_ring_ctrl(struct mei_device *dev) |
| 103 | { |
| 104 | return (struct hbm_dma_ring_ctrl *)dev->dr_dscr[DMA_DSCR_CTRL].vaddr; |
| 105 | } |
| 106 | |
| 107 | /** |
| 108 | * mei_dma_ring_reset() - reset the dma control block |
| 109 | * @dev: mei device |
| 110 | */ |
| 111 | void mei_dma_ring_reset(struct mei_device *dev) |
| 112 | { |
| 113 | struct hbm_dma_ring_ctrl *ctrl = mei_dma_ring_ctrl(dev); |
| 114 | |
| 115 | if (!ctrl) |
| 116 | return; |
| 117 | |
| 118 | memset(ctrl, 0, sizeof(*ctrl)); |
| 119 | } |
Tomas Winkler | 6316321 | 2018-11-22 13:11:39 +0200 | [diff] [blame] | 120 | |
| 121 | /** |
| 122 | * mei_dma_copy_from() - copy from dma ring into buffer |
| 123 | * @dev: mei device |
| 124 | * @buf: data buffer |
| 125 | * @offset: offset in slots. |
| 126 | * @n: number of slots to copy. |
| 127 | */ |
| 128 | static size_t mei_dma_copy_from(struct mei_device *dev, unsigned char *buf, |
| 129 | u32 offset, u32 n) |
| 130 | { |
| 131 | unsigned char *dbuf = dev->dr_dscr[DMA_DSCR_DEVICE].vaddr; |
| 132 | |
| 133 | size_t b_offset = offset << 2; |
| 134 | size_t b_n = n << 2; |
| 135 | |
| 136 | memcpy(buf, dbuf + b_offset, b_n); |
| 137 | |
| 138 | return b_n; |
| 139 | } |
| 140 | |
| 141 | /** |
Tomas Winkler | c30362c | 2018-11-22 13:11:40 +0200 | [diff] [blame] | 142 | * mei_dma_copy_to() - copy to a buffer to the dma ring |
| 143 | * @dev: mei device |
| 144 | * @buf: data buffer |
| 145 | * @offset: offset in slots. |
| 146 | * @n: number of slots to copy. |
| 147 | */ |
| 148 | static size_t mei_dma_copy_to(struct mei_device *dev, unsigned char *buf, |
| 149 | u32 offset, u32 n) |
| 150 | { |
| 151 | unsigned char *hbuf = dev->dr_dscr[DMA_DSCR_HOST].vaddr; |
| 152 | |
| 153 | size_t b_offset = offset << 2; |
| 154 | size_t b_n = n << 2; |
| 155 | |
| 156 | memcpy(hbuf + b_offset, buf, b_n); |
| 157 | |
| 158 | return b_n; |
| 159 | } |
| 160 | |
| 161 | /** |
Tomas Winkler | 6316321 | 2018-11-22 13:11:39 +0200 | [diff] [blame] | 162 | * mei_dma_ring_read() - read data from the ring |
| 163 | * @dev: mei device |
| 164 | * @buf: buffer to read into: may be NULL in case of droping the data. |
| 165 | * @len: length to read. |
| 166 | */ |
| 167 | void mei_dma_ring_read(struct mei_device *dev, unsigned char *buf, u32 len) |
| 168 | { |
| 169 | struct hbm_dma_ring_ctrl *ctrl = mei_dma_ring_ctrl(dev); |
| 170 | u32 dbuf_depth; |
| 171 | u32 rd_idx, rem, slots; |
| 172 | |
| 173 | if (WARN_ON(!ctrl)) |
| 174 | return; |
| 175 | |
| 176 | dev_dbg(dev->dev, "reading from dma %u bytes\n", len); |
| 177 | |
| 178 | if (!len) |
| 179 | return; |
| 180 | |
| 181 | dbuf_depth = dev->dr_dscr[DMA_DSCR_DEVICE].size >> 2; |
| 182 | rd_idx = READ_ONCE(ctrl->dbuf_rd_idx) & (dbuf_depth - 1); |
| 183 | slots = mei_data2slots(len); |
| 184 | |
| 185 | /* if buf is NULL we drop the packet by advancing the pointer.*/ |
| 186 | if (!buf) |
| 187 | goto out; |
| 188 | |
| 189 | if (rd_idx + slots > dbuf_depth) { |
| 190 | buf += mei_dma_copy_from(dev, buf, rd_idx, dbuf_depth - rd_idx); |
| 191 | rem = slots - (dbuf_depth - rd_idx); |
| 192 | rd_idx = 0; |
| 193 | } else { |
| 194 | rem = slots; |
| 195 | } |
| 196 | |
| 197 | mei_dma_copy_from(dev, buf, rd_idx, rem); |
| 198 | out: |
| 199 | WRITE_ONCE(ctrl->dbuf_rd_idx, ctrl->dbuf_rd_idx + slots); |
| 200 | } |
Tomas Winkler | c30362c | 2018-11-22 13:11:40 +0200 | [diff] [blame] | 201 | |
| 202 | static inline u32 mei_dma_ring_hbuf_depth(struct mei_device *dev) |
| 203 | { |
| 204 | return dev->dr_dscr[DMA_DSCR_HOST].size >> 2; |
| 205 | } |
| 206 | |
| 207 | /** |
| 208 | * mei_dma_ring_empty_slots() - calaculate number of empty slots in dma ring |
| 209 | * @dev: mei_device |
| 210 | * |
| 211 | * Return: number of empty slots |
| 212 | */ |
| 213 | u32 mei_dma_ring_empty_slots(struct mei_device *dev) |
| 214 | { |
| 215 | struct hbm_dma_ring_ctrl *ctrl = mei_dma_ring_ctrl(dev); |
| 216 | u32 wr_idx, rd_idx, hbuf_depth, empty; |
| 217 | |
| 218 | if (!mei_dma_ring_is_allocated(dev)) |
| 219 | return 0; |
| 220 | |
| 221 | if (WARN_ON(!ctrl)) |
| 222 | return 0; |
| 223 | |
| 224 | /* easier to work in slots */ |
| 225 | hbuf_depth = mei_dma_ring_hbuf_depth(dev); |
| 226 | rd_idx = READ_ONCE(ctrl->hbuf_rd_idx); |
| 227 | wr_idx = READ_ONCE(ctrl->hbuf_wr_idx); |
| 228 | |
| 229 | if (rd_idx > wr_idx) |
| 230 | empty = rd_idx - wr_idx; |
| 231 | else |
| 232 | empty = hbuf_depth - (wr_idx - rd_idx); |
| 233 | |
| 234 | return empty; |
| 235 | } |
| 236 | |
| 237 | /** |
| 238 | * mei_dma_ring_write - write data to dma ring host buffer |
| 239 | * |
| 240 | * @dev: mei_device |
| 241 | * @buf: data will be written |
| 242 | * @len: data length |
| 243 | */ |
| 244 | void mei_dma_ring_write(struct mei_device *dev, unsigned char *buf, u32 len) |
| 245 | { |
| 246 | struct hbm_dma_ring_ctrl *ctrl = mei_dma_ring_ctrl(dev); |
| 247 | u32 hbuf_depth; |
| 248 | u32 wr_idx, rem, slots; |
| 249 | |
| 250 | if (WARN_ON(!ctrl)) |
| 251 | return; |
| 252 | |
| 253 | dev_dbg(dev->dev, "writing to dma %u bytes\n", len); |
| 254 | hbuf_depth = mei_dma_ring_hbuf_depth(dev); |
| 255 | wr_idx = READ_ONCE(ctrl->hbuf_wr_idx) & (hbuf_depth - 1); |
| 256 | slots = mei_data2slots(len); |
| 257 | |
| 258 | if (wr_idx + slots > hbuf_depth) { |
| 259 | buf += mei_dma_copy_to(dev, buf, wr_idx, hbuf_depth - wr_idx); |
| 260 | rem = slots - (hbuf_depth - wr_idx); |
| 261 | wr_idx = 0; |
| 262 | } else { |
| 263 | rem = slots; |
| 264 | } |
| 265 | |
| 266 | mei_dma_copy_to(dev, buf, wr_idx, rem); |
| 267 | |
| 268 | WRITE_ONCE(ctrl->hbuf_wr_idx, ctrl->hbuf_wr_idx + slots); |
| 269 | } |