1/*
2 * Copyright (c) 2011 ETH Zurich.
3 * All rights reserved.
4 *
5 * This file is distributed under the terms in the attached LICENSE file.
6 * If you do not find this file, copies can be found by writing to:
7 * ETH Zurich D-INFK, Haldeneggsteig 4, CH-8092 Zurich. Attn: Systems Group.
8 */
9
10#include <barrelfish/barrelfish.h>
11#include <barrelfish/memobj.h>
12#include <barrelfish/vregion.h>
13#include <ahci/ahci.h>
14#include <ahci/ahci_dma_pool.h>
15#include <dev/ahci_hba_dev.h>
16#include <string.h>
17#include "ahci_debug.h"
18#include "ahci_internal.h"
19
20#define AHCI_MAX_PRD_COUNT 65535
21
22// CL is 1K, see AHCI Spec 1.3 Section 3.3.1
23#define AHCI_CL_SIZE 1024
24
25// rFIS is 256 bytes, see AHCI Spec 1.3 Section 3.3.3
26#define AHCI_RFIS_SIZE 256
27
28
29int ahci_find_free_command_slot(struct ahci_port_info *port)
30{
31    for (int i = 0; i < ahci_hba_cap_ncs_extract(port->hba_capabilities) + 1; i++) {
32        if (!port->command_slots[i].in_use) {
33            return i;
34        }
35    }
36
37    return -1;
38}
39
40errval_t ahci_port_alloc_dma_structs(ahci_port_t *port,
41        struct ahci_dma_region **command_list,
42        struct ahci_dma_region **receive_fis)
43{
44    errval_t r;
45
46    // allocate frame for command header list
47    r = ahci_dma_region_alloc_aligned(AHCI_CL_SIZE, AHCI_CL_SIZE,
48            command_list);
49    assert(err_is_ok(r));
50    // CLB must be aligned to 1024 bytes
51    assert(((*command_list)->paddr & 0x3FF) == 0);
52    ahci_port_clb_wr(port, (*command_list)->paddr);
53
54    // allocate frame for received FIS
55    r = ahci_dma_region_alloc_aligned(AHCI_RFIS_SIZE, AHCI_RFIS_SIZE,
56           receive_fis);
57    assert(err_is_ok(r));
58    // FIS must be aligned to 256 bytes
59    assert(((*receive_fis)->paddr & 0xFF) == 0);
60    ahci_port_fb_wr(port, (*receive_fis)->paddr);
61
62    return 0;
63}
64
65void ahci_port_free_dma_structs(struct ahci_port_info *port)
66{
67    errval_t err;
68    err = ahci_dma_region_free(port->command_list);
69    if (err_is_fail(err)) {
70        USER_PANIC_ERR(err, "ahci_dma_region_free failed for port command list");
71    }
72    err = ahci_dma_region_free(port->receive_fis);
73    if (err_is_fail(err)) {
74        USER_PANIC_ERR(err, "ahci_dma_region_free failed for port receive fis");
75    }
76}
77
78errval_t ahci_setup_command(int *command, struct ahci_port_info *port,
79        uint8_t *fis, size_t fis_length, size_t num_prds, bool is_write)
80{
81    errval_t r;
82    AHCI_DEBUG("ahci_setup_command(%p, %p, %p, %zu, %zu, %d): entering\n",
83            command, port, fis, fis_length, num_prds, is_write);
84
85    int command_slot = ahci_find_free_command_slot(port);
86    assert(command_slot != -1); //TODO: what to do if we run out of slots? wait?
87    port->command_slots[command_slot].in_use = true;
88
89    // setup command table w/ enough entries for PRDs
90    size_t prdt_size = num_prds*ahci_port_prd_size;
91    size_t cmd_table_size = PRDT_OFFSET + prdt_size;
92    struct ahci_dma_region *ct;
93    r = ahci_dma_region_alloc(cmd_table_size, &ct);
94    if (err_is_fail(r)) {
95        DEBUG_ERR(r, "failed to allocate memory for command table");
96        return r;
97    }
98    port->command_slots[command_slot].command_table = ct;
99
100
101    AHCI_DEBUG("Using command slot: %d\n", command_slot);
102    // insert command table into command header at pos `next_cmd'
103    uint8_t *command_header_entry =
104        ((uint8_t*)port->command_list->vaddr) + command_slot*ahci_port_chdr_size;
105    memset(command_header_entry, 0, ahci_port_chdr_size);
106    ahci_port_chdr_t chdr = (ahci_port_chdr_t) command_header_entry;
107    ahci_port_chdr_cfl_insert(chdr, fis_length / 4);
108    ahci_port_chdr_w_insert(chdr, is_write);
109    ahci_port_chdr_ctba_insert(chdr, ct->paddr);
110
111    // copy fis into command table
112    memset(ct->vaddr, 0, cmd_table_size);
113    ahci_dma_region_copy_in(ct, fis, 0, fis_length);
114
115    *command = command_slot;
116
117    AHCI_DEBUG("ahci_setup_command: exiting\n");
118    return SYS_ERR_OK;
119}
120
121static void ahci_set_physical_region(ahci_port_prd_t prd,
122        genpaddr_t physical_base, size_t length)
123{
124    //AHCI_DEBUG("ahci_set_physical_region: entering\n");
125#ifdef AHCI_FIXED_PR_SIZE
126    assert(length == PR_SIZE);
127#else
128    assert(length <= MAX_PR_SIZE);
129    assert((length & 1) == 0);
130#endif
131
132    ahci_port_prd_dba_insert(prd, (uint32_t)physical_base);
133    ahci_port_prd_dbau_insert(prd, (uint32_t)(physical_base >> 32));
134    ahci_port_prd_dbc_insert(prd, length-1);
135    //AHCI_DEBUG("ahci_set_physical_region: exiting\n");
136}
137
138static inline genpaddr_t ahci_get_sector_paddr(struct ahci_dma_region *region,
139        size_t offset)
140{
141    return region->paddr + offset;
142}
143
144errval_t ahci_add_physical_regions(struct ahci_port_info *port,
145        int command, struct ahci_dma_region *buf, size_t buflen)
146{
147    AHCI_DEBUG("ahci_add_physical_regions: entering\n");
148    uint8_t *command_header_base =
149        ((uint8_t*)port->command_list->vaddr) + command*ahci_port_chdr_size;
150    ahci_port_chdr_t chdr = (ahci_port_chdr_t) command_header_base;
151
152    // find next free prd entry in command header
153    size_t prd_count = ahci_port_chdr_prdtl_extract(chdr);
154    AHCI_DEBUG("prd_count = %zd\n", prd_count);
155    // position for next prd
156    size_t prdt_index = prd_count;
157    // get base address of prd table
158    uint8_t *prdt_base =
159        (uint8_t*)port->command_slots[command].command_table->vaddr +
160        PRDT_OFFSET;
161
162    // max 512 bytes per PR
163#ifdef AHCI_FIXED_PR_SIZE
164    size_t num_prds_needed = CEIL_DIV(buflen, PR_SIZE);
165#else
166    size_t num_prds_needed = CEIL_DIV(buflen, MAX_PR_SIZE);
167#endif
168    AHCI_DEBUG("#prds = %zd\n", num_prds_needed);
169
170    // check that remaining space is enough for requested length
171    if (prdt_index + num_prds_needed >= AHCI_MAX_PRD_COUNT) {
172        // TODO: error code
173	AHCI_DEBUG("not enough prd space left.\n");
174        return -1;
175    }
176
177    AHCI_DEBUG("PRDT base: %p\n", prdt_base);
178    // initialize a prd for each region
179    ahci_port_prd_t prd;
180    size_t offset = 0;
181    do {
182        prd = (ahci_port_prd_t) prdt_base + (prdt_index * ahci_port_prd_size);
183        memset(prd, 0, ahci_port_prd_size);
184        genpaddr_t sector_addr = ahci_get_sector_paddr(buf, offset);
185        //AHCI_DEBUG("sector_addr = 0x%lx\n", sector_addr);
186#ifdef AHCI_FIXED_PR_SIZE
187        ahci_set_physical_region(prd, sector_addr, PR_SIZE);
188        buflen -= PR_SIZE;
189        offset += PR_SIZE;
190#else
191        size_t chunksize = buflen < MAX_PR_SIZE ? buflen : MAX_PR_SIZE;
192        ahci_set_physical_region(prd, sector_addr, chunksize);
193        buflen -= chunksize;
194        offset += chunksize;
195#endif
196        prdt_index += 1;
197    } while (buflen > 0);
198
199    // update number of PRDs for command
200    ahci_port_chdr_prdtl_insert(chdr, prdt_index);
201
202    AHCI_DEBUG("ahci_add_physical_regions: exiting\n");
203    return 0;
204}
205
206#if defined(AHCI_LIB_DEBUG) || defined(GLOBAL_DEBUG)
207void ahci_dump_command(int command, struct ahci_port_info *port)
208{
209    char buf_[4096];
210    ahci_port_chdr_t chdr =
211        (ahci_port_chdr_t)((uint8_t*) port->command_list->vaddr) +
212        command*ahci_port_chdr_size;
213    ahci_port_chdr_prtval(buf_, 4096, chdr);
214    AHCI_DEBUG("\n%s\n", buf_);
215    int prd_count = ahci_port_chdr_prdtl_extract(chdr);
216    ahci_port_prd_t prd;
217    for (int k = 0; k < prd_count; k++) {
218        uint8_t *cmd_table_base = (uint8_t*) port->command_slots[command].command_table->vaddr;
219        prd = (ahci_port_prd_t) (cmd_table_base + PRDT_OFFSET + k*ahci_port_prd_size);
220        ahci_port_prd_prtval(buf_, 4096, prd);
221        AHCI_DEBUG("\nPRD %d:\n%s\n", k, buf_);
222    }
223}
224#else
225void ahci_dump_command(int command, struct ahci_port_info *port) {}
226#endif
227
228#if defined(AHCI_LIB_DEBUG) || defined(GLOBAL_DEBUG)
229void ahci_dump_rfis(struct ahci_port_info *port)
230{
231    printf("rfis:\n");
232    for (int i = 0; i < 256; i += 4) {
233        printf("%3x: 0x%02x 0x%02x 0x%02x 0x%02x\n", i,
234            *(((uint8_t*)port->receive_fis->vaddr) + i),
235            *(((uint8_t*)port->receive_fis->vaddr) + i + 1),
236            *(((uint8_t*)port->receive_fis->vaddr) + i + 2),
237            *(((uint8_t*)port->receive_fis->vaddr) + i + 3)
238        );
239    }
240}
241#else
242void ahci_dump_rfis(struct ahci_port_info *port) {}
243#endif
244