Skip to content
This repository has been archived by the owner on Jan 10, 2022. It is now read-only.

Feature/rtt support #21

Merged
merged 43 commits into from
Nov 16, 2017
Merged

Feature/rtt support #21

merged 43 commits into from
Nov 16, 2017

Conversation

CoqRogue
Copy link
Contributor

Added RTT.

Documentation is not done, will be a separate PR as soon as it is done.

Per Kristian Schanke added 30 commits October 24, 2017 13:48
…for jlinkarm location, clockspeed, and device family. Also some interface cleanup for loading libraries.

Linux and OS X will not work with this commit due to statically looking up the .dll files for highlevel and nrfjprog library. This will be fixed shortly.
# Conflicts:
#	src/highlevel.cpp
#	src/highlevel.h
#	src/highlevel_common.h
#	src/highlevel_helpers.h
#	src/platform/linux/libraryloader.cpp
#	src/platform/osx/libraryloader.cpp
Add timestamps to get an idea on when data is produced on the device.
#define micro_version (1)


enum NrfjprogErrorCodesType {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's wrong with indentation here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a file included from nRFjprog. No need to review it.

src/rtt_batons.h Outdated
uint32_t result;
nrfjprogdll_err_t lowlevelError;

uv_work_t *req;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::unique_ptr would be much safer and simpler.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will consider it.


if (justMicrosecondPart)
{
duration = duration % 1000;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be only the least significant 3 decimal digits of the microseconds part.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is correct. If you want only the microseconds, you call this function with the last parameter set to true. This is mostly for occasions where you might want to indicate the time as x min, yy s, zzz ms, æææ us

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see such use-case, and by human understanding I'd expect duration % 1e6 as result.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that the usecase is not there anymore. Will remove this code as well as a couple of conversion functions not needed anymore.

Copy link
Contributor

@IvanSanchez IvanSanchez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks as incomprehensible as the existing nrfjprog-js code (which is not a bad thing 😉).

LGTM, specially as there are automated tests 👍

Still needs documentation 😞 but that can be done at a later PR.


enum NrfjprogErrorCodesType {

Success = 0, // Requested operation (operations) were successfully completed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check whitespace. Looks funky.

return errorcode_t::CouldNotLoadDLL;
}

if (!load_func_ptr(&dll_function->dll_version, "NRFJPROG_dll_version", nrfJproglibraryHandle)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the return value of all these ifs is the same, this might look better as a big chain of newline-separated || statements

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would make it slightly harder to debug as it is harder to set break points. Other than that it is a good idea. Will consider into it.

src/rtt.cpp Outdated

void RTT::logCallback(const char *msg)
{
log(msg);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, what does this do? Just call log()? Does it work despite logCallback receiving *msg and log receiving std::string? Couldn't the calls to logCallback just be renamed into log???

😕

CallFunction(info, p, e, nullptr, true);
}

#include "rtt.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#includes should be hoisted to the top of the file

src/rtt.cpp Outdated
RETURN_ERROR_ON_FAIL(dll_function.rtt_start(), RTTCouldNotStartRTT);


while (true) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This loop is polling. Plz consider adding something like sleep(25).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would sleep 25 seconds.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/sleep/usleep

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will fix.

src/rtt.cpp Outdated

RETURN_ERROR_ON_FAIL(dll_function.rtt_read_channel_count(&downChannelNumber, &upChannelNumber), RTTCouldNotGetChannelInformation);

for(uint32_t i = 0; i < downChannelNumber; ++i)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A uint32_t to iterate channel numbers? Are we having more than 65K channels on a RTT? LOL 🤣

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uint32_t covers slightly more then 65K, is it not, or what really is the issue? If the external library uses that type to return channel number, then let it be.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is, as @bencefr also mentions, due to rtt_read_channel_count utilize a uint32_t. To avoid the loops producing compile time warnings, it is recommended to use the same type in the loop.


releasenRFjprog();

libraryLoaded = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There might be an edge case / race condition here. Are there any instances where cleanup might occur twice in quick succession (in a shorter time than it takes for the DLL stuff to unload)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right in that race conditions may occur. The current implementation expects that the user is nice and never calls a function before the previous callback was completed. Will consider a fix for this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have added an execution mutex that locks with a timeout of 10 seconds on all function calls. All race conditions should be handled by that. Two tests were written to test this, and one of them confirms that the guards work by first calling start, and then call read three times in a row. With previous implementation, the reads would return with an error that rtt was not started. Currently they return without error.

Attempting to hit the 10 second timeout for the mutex proved harder. Even with 1M calls to read in a row as fast as possible, you would never get an error. But I have implemented the same functionality for nRFjprog, and when I attempt to program multiple times as fast as possible, the timeout triggers. This can be seen in __tests__/racecondition.test.js

src/rtt_batons.h Outdated
#include "rtt.h"
#include "rtt_helpers.h"

#define RTTBATON_CONSTRUCTOR(BatonType, name, returnParameterCount) BatonType() : RTTBaton(returnParameterCount, name) {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that this macro serves any purpose, besides it also changes order of arguments.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only reason this has survived is due to initially wanting to keep it as similar to the highlevel implementation as possible as well as pc-ble-driver-js, but it is redundant here, and I will remove it. Will also consider removing it in highlevel implementation as well.

src/rtt_batons.h Outdated
#include "rtt_helpers.h"

#define RTTBATON_CONSTRUCTOR(BatonType, name, returnParameterCount) BatonType() : RTTBaton(returnParameterCount, name) {}
#define RTTBATON_DESTRUCTOR(BatonType) ~BatonType()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will remove.

src/rtt_batons.h Outdated
bool hasControlBlockLocation;
uint32_t controlBlockLocation;

std::vector<ChannelInfo *> upChannelInfo;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These vectors never seem to be freed, the contents of them I mean. Usage of smart pointers are encouraged.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will consider it.

src/rtt.cpp Outdated
while (true) {
bool controlBlockFound = false;
RETURN_ERROR_ON_FAIL(dll_function.rtt_is_control_block_found(&controlBlockFound), RTTCouldNotCallFunction);
std::chrono::high_resolution_clock::time_point attemptedStartupTime = std::chrono::high_resolution_clock::now();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only used if controlBlockFound is false. Move it after break condition along with a sensible sleep.

{
if (baton->returnFunction != nullptr)
{
std::vector<v8::Local<v8::Value> > vector = baton->returnFunction(baton);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using variable names matching standard typenames I don't suggest in general, misleading to read.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will fix.

src/rtt.cpp Outdated
return RTTSuccess;
};

rtt_return_function_t r = [&] (RTTBaton *b) -> returnType {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

returnType might be a magic that the compiler solves here, but does not allow a readable code. Also again vector is a name that I would avoid using.

return baton;
};

rtt_execute_function_t e = [&] (RTTBaton *b) -> RTTErrorcodes_t {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rtt_execute_function_t e = [&] (RTTStartBaton *baton), so you don't need static_cast which should not even exist in C++.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This suggested change results in a compile error, so will keep current implementation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see I was wrong, since the function type requires RTTBaton*, however there are better casts probably for this case. Never mind.

src/rtt.cpp Outdated
return started;
}

NAN_METHOD(RTT::Start)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waaaaay too long function, split it up, especially the execute part.

src/rtt.cpp Outdated
for (auto element : baton->downChannelInfo)
{
Nan::Set(downChannelInfo, Convert::toJsNumber(i), element->ToJs());
i++;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i++ is bad practice, no matter how long you can talk about compiler magic. Get used to use ++i.

@bencefr
Copy link
Contributor

bencefr commented Nov 16, 2017

In my opinion - in the light of the threading issues - it is a bad approach to lock everything out so brutally. Especially that the library load/unload is also part of this (however it was much much more unsafe before).

The problem is that we are tackling a problem here which is not the context of this feature PR. Here's what has to be done:

  1. finally solve the library loading part, do NOT load and unload for each call.
  2. whatever function is predictably trivial in execution shall be implemented WITHOUT creating a baton and queuing on UV work thread pool, e.g. GetDllVersion().
  3. map all the data that is shared between potential threads and protect the data by locking them individually for the shortest possible access.
  4. consider using a separate UV loop than the default, configure it for your own purposes.

@CoqRogue
Copy link
Contributor Author

In principle, I agree with you @bencefr, there are, however, some other considerations to make.

  1. We have had so many issues with locking the drivers underneath in previous projects. One usecase that often pop up is that people want to do other operations, like programming with the nrfjprog command line tools. And when we load the nrfjprog drivers, they will not be able to perform those operations.
  2. There are a couple of functions, including GetDllVersion, that are fast enough to be executed synchronously without lagging the js-thread. In my opinion, api consistency is more important than advantages to we may get due to moving them to a synchronous api.
  3. I have mapped the data, and we are locking for a minimum time. The speedups we could get by moving the locks is in the a few clock cycles.
  4. I cannot see what kind of separate UV loop that we could setup which would be more practical or useful.

@bencefr
Copy link
Contributor

bencefr commented Nov 16, 2017

@CoqRogue ,

  1. It is a good thing that you cannot perform anything on an open device while some other application keeps it open. Therefore API has to provide an open/close functionality, which MUST be explicitly called. It is the responsibility of the application using this library to close a device when not used.
  2. API consistency has nothing to do with unnecessarily invoking an other thread.
  3. Loading and unloading a library I do not believe to in the few clock cycle range, especially on Windows.
  4. Separate EV loop was only a suggestion, I am not very familiar with it, but if that would give more control of the contexts it would worth spending a bit of time to investigate. Maybe not.

index.js Outdated
@@ -44,3 +44,4 @@
});

module.exports = instance;
module.exports.RTT = nRFjprog.RTT;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about changing this to module.exports.RTT = nRFjprog.RTT()?

I'd prefer the module exporting a RTT singleton, instead of a RTT constructor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Will fix.

@CoqRogue
Copy link
Contributor Author

  1. nRFjprog has an optional open/close mechanism which will not load/unload the library. RTT has start which loads the library, and stop that unloads it.
  2. API consistency is one of many factors to consider. In this case I find consistency more important than the minimal gain you could get with doing this synchronously. It is also easier to maintain.
  3. The lock is locked just before the first variable that could come into a race condition and unlocked on any return. The clock cycles I am talking about are the ones at the end of HighLevel::ExecuteFunction: The if (executeError != SUCCESS) {...} could be moved to the bottom of the function and unlock could have been called explicitly right before. We are most likely talking 10-100 clock cycles.
  4. I agree that it could be worth an investigation, but I am not sure that this is the time for it.

@bencefr
Copy link
Contributor

bencefr commented Nov 16, 2017

  1. the mutex lock is inside the execute function which also locks loadDll() and unloadDll().
  2. I'm not saying it has to be fixed for this PR, this one obviously works. But this has to be addressed later.

@CoqRogue CoqRogue merged commit f9fd70c into master Nov 16, 2017
@CoqRogue CoqRogue deleted the feature/rtt-support branch November 16, 2017 12:38
@bihanssen
Copy link
Contributor

Good job on the code&review all, looks good.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants