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