1// Copyright 2018 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "platform-i2c.h"
6
7#include <ddk/debug.h>
8#include <fbl/array.h>
9#include <fbl/auto_lock.h>
10#include <fbl/unique_ptr.h>
11#include <stdlib.h>
12#include <threads.h>
13#include <zircon/assert.h>
14#include <zircon/listnode.h>
15#include <zircon/threads.h>
16
17namespace platform_bus {
18
19PlatformI2cBus::PlatformI2cBus(i2c_impl_protocol_t* i2c, uint32_t bus_id)
20    : i2c_(i2c), bus_id_(bus_id) {
21
22    list_initialize(&queued_txns_);
23    list_initialize(&free_txns_);
24    sync_completion_reset(&txn_signal_);
25}
26
27zx_status_t PlatformI2cBus::Start() {
28    auto status = i2c_.GetMaxTransferSize(bus_id_, &max_transfer_);
29    if (status != ZX_OK) {
30        return status;
31    }
32    if (max_transfer_ > I2C_MAX_TRANSFER_SIZE) {
33        max_transfer_ = I2C_MAX_TRANSFER_SIZE;
34    }
35
36    char name[32];
37    snprintf(name, sizeof(name), "PlatformI2cBus[%u]", bus_id_);
38    auto thunk = [](void* arg) -> int { return static_cast<PlatformI2cBus*>(arg)->I2cThread(); };
39    thrd_create_with_name(&thread_, thunk, this, name);
40
41    return ZX_OK;
42}
43
44void PlatformI2cBus::Complete(I2cTxn* txn, zx_status_t status, const uint8_t* resp_buffer,
45                              size_t resp_length) {
46    rpc_i2c_rsp_t* i2c = (rpc_i2c_rsp_t*)(resp_buffer);
47    i2c->header.txid = txn->txid;
48    i2c->header.status = status;
49    i2c->max_transfer = 0;
50    i2c->transact_cb = txn->transact_cb;
51    i2c->cookie = txn->cookie;
52    status = zx_channel_write(txn->channel_handle, 0, resp_buffer,
53                              static_cast<uint32_t>(resp_length), nullptr, 0);
54    if (status != ZX_OK) {
55        zxlogf(ERROR, "platform_i2c_read_complete: zx_channel_write failed %d\n", status);
56    }
57}
58
59int PlatformI2cBus::I2cThread() {
60    fbl::AllocChecker ac;
61    fbl::Array<uint8_t> read_buffer(new (&ac) uint8_t[PROXY_MAX_TRANSFER_SIZE],
62                                    PROXY_MAX_TRANSFER_SIZE);
63    if (!ac.check()) {
64        zxlogf(ERROR, "%s could not allocate read_buffer\n", __FUNCTION__);
65        return 0;
66    }
67
68    while (1) {
69        sync_completion_wait(&txn_signal_, ZX_TIME_INFINITE);
70        sync_completion_reset(&txn_signal_);
71
72        I2cTxn* txn;
73
74        mutex_.Acquire();
75        while ((txn = list_remove_head_type(&queued_txns_, I2cTxn, node)) != nullptr) {
76            mutex_.Release();
77            auto rpc_ops = reinterpret_cast<i2c_rpc_op_t*>(txn + 1);
78            auto p_writes = reinterpret_cast<uint8_t*>(rpc_ops) +
79                txn->cnt * sizeof(i2c_rpc_op_t);
80            uint8_t* p_reads = read_buffer.get() + sizeof(rpc_i2c_rsp_t);
81
82            ZX_ASSERT(txn->cnt < I2C_MAX_RW_OPS);
83            i2c_impl_op_t ops[I2C_MAX_RW_OPS];
84            for (size_t i = 0; i < txn->cnt; ++i) {
85                // Same address for all ops, since there is one address per channel.
86                ops[i].address = txn->address;
87                ops[i].length = rpc_ops[i].length;
88                ops[i].is_read = rpc_ops[i].is_read;
89                ops[i].stop = rpc_ops[i].stop;
90                if (ops[i].is_read) {
91                    ops[i].buf = p_reads;
92                    p_reads += ops[i].length;
93                } else {
94                    ops[i].buf = p_writes;
95                    p_writes += ops[i].length;
96                }
97            }
98            auto status = i2c_.Transact(bus_id_, ops, txn->cnt);
99            size_t actual = status == ZX_OK ? p_reads - read_buffer.get() : sizeof(rpc_i2c_rsp_t);
100            Complete(txn, status, read_buffer.get(), actual);
101
102            mutex_.Acquire();
103            list_add_tail(&free_txns_, &txn->node);
104        }
105        mutex_.Release();
106    }
107    return 0;
108}
109
110zx_status_t PlatformI2cBus::Transact(uint32_t txid, rpc_i2c_req_t* req, uint16_t address,
111                                     zx_handle_t channel_handle) {
112    i2c_rpc_op_t* ops = reinterpret_cast<i2c_rpc_op_t*>(req + 1);
113
114    size_t writes_length = 0;
115    for (size_t i = 0; i < req->cnt; ++i) {
116        if (ops[i].length == 0 || ops[i].length > max_transfer_) {
117            return ZX_ERR_INVALID_ARGS;
118        }
119        if (!ops[i].is_read) {
120            writes_length += ops[i].length;
121        }
122    }
123    // Add space for requests and writes data.
124    size_t req_length = sizeof(I2cTxn) + req->cnt * sizeof(i2c_rpc_op_t) + writes_length;
125    if (req_length >= PROXY_MAX_TRANSFER_SIZE) {
126        return ZX_ERR_INVALID_ARGS;
127    }
128
129    fbl::AutoLock lock(&mutex_);
130
131    I2cTxn* txn = list_remove_head_type(&free_txns_, I2cTxn, node);
132    if (txn && txn->length < req_length) {
133        free(txn);
134        txn = nullptr;
135    }
136
137    if (!txn) {
138        // add space for write buffer
139        txn = static_cast<I2cTxn*>(calloc(1, req_length));
140        txn->length = req_length;
141    }
142    if (!txn) {
143        return ZX_ERR_NO_MEMORY;
144    }
145
146    txn->address = address;
147    txn->txid = txid;
148    txn->transact_cb = req->transact_cb;
149    txn->cookie = req->cookie;
150    txn->channel_handle = channel_handle;
151    txn->cnt = req->cnt;
152
153    auto rpc_ops = reinterpret_cast<i2c_rpc_op_t*>(req + 1);
154    if (req->cnt && !(rpc_ops[req->cnt - 1].stop)) {
155        list_add_tail(&free_txns_, &txn->node);
156        return ZX_ERR_INVALID_ARGS; // no stop in last op in transaction
157    }
158
159    memcpy(txn + 1, req + 1, req->cnt * sizeof(i2c_rpc_op_t) + writes_length);
160
161    list_add_tail(&queued_txns_, &txn->node);
162    sync_completion_signal(&txn_signal_);
163
164    return ZX_OK;
165}
166
167} // namespace platform_bus
168