| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package nmble |
| |
| import ( |
| "fmt" |
| "sync" |
| |
| log "github.com/sirupsen/logrus" |
| |
| "mynewt.apache.org/newtmgr/nmxact/nmxutil" |
| ) |
| |
| type masterState int |
| |
| const ( |
| MASTER_STATE_IDLE masterState = iota |
| MASTER_STATE_SECONDARY |
| MASTER_STATE_PRIMARY |
| MASTER_STATE_PRIMARY_SECONDARY_PENDING |
| ) |
| |
| func (s masterState) String() string { |
| m := map[masterState]string{ |
| MASTER_STATE_IDLE: "idle", |
| MASTER_STATE_SECONDARY: "secondary", |
| MASTER_STATE_PRIMARY: "primary", |
| MASTER_STATE_PRIMARY_SECONDARY_PENDING: "primary_secondary_pending", |
| } |
| |
| return m[s] |
| } |
| |
| type primary struct { |
| token interface{} |
| ch chan error |
| } |
| |
| type Preemptable interface { |
| Preempt() error |
| } |
| |
| // Represents the Bluetooth device's "master privileges." The device can only |
| // do one of the following actions at a time: |
| // * initiate connection |
| // * scan |
| // |
| // Clients are divided into two groups: |
| // * primary |
| // * secondary |
| // |
| // This struct restricts master privileges to a single client at a time. It |
| // uses the following procedure to determine which of several clients to serve: |
| // If there is one or more waiting primaries: |
| // If a secondary is active, preempt it. |
| // Service the primaries in the order of their requests. |
| // Else (no waiting primaries): |
| // Service waiting secondary if there is one. |
| type Master struct { |
| primaries []primary |
| secondary Preemptable |
| state masterState |
| secondaryReadyCh chan error |
| |
| mtx sync.Mutex |
| } |
| |
| func NewMaster(x *BleXport) Master { |
| return Master{ |
| secondaryReadyCh: make(chan error), |
| } |
| } |
| |
| func (m *Master) GetSecondary() Preemptable { |
| return m.secondary |
| } |
| |
| func (m *Master) SetSecondary(s Preemptable) error { |
| m.mtx.Lock() |
| defer m.mtx.Unlock() |
| |
| if m.state == MASTER_STATE_SECONDARY || |
| m.state == MASTER_STATE_PRIMARY_SECONDARY_PENDING { |
| |
| return fmt.Errorf("cannot replace master secondary while it is in use") |
| } |
| |
| m.secondary = s |
| return nil |
| } |
| |
| func (m *Master) setState(s masterState) { |
| log.Debugf("Master state change: %s --> %s", m.state, s) |
| m.state = s |
| } |
| |
| func (m *Master) appendPrimary(token interface{}) primary { |
| c := primary{ |
| token: token, |
| ch: make(chan error), |
| } |
| m.primaries = append(m.primaries, c) |
| |
| return c |
| } |
| |
| func (m *Master) AcquirePrimary(token interface{}) error { |
| initiate := func() (*primary, error) { |
| m.mtx.Lock() |
| defer m.mtx.Unlock() |
| |
| switch m.state { |
| case MASTER_STATE_IDLE: |
| m.setState(MASTER_STATE_PRIMARY) |
| return nil, nil |
| |
| case MASTER_STATE_SECONDARY: |
| c := m.appendPrimary(token) |
| go m.secondary.Preempt() |
| return &c, nil |
| |
| case MASTER_STATE_PRIMARY, MASTER_STATE_PRIMARY_SECONDARY_PENDING: |
| c := m.appendPrimary(token) |
| return &c, nil |
| |
| default: |
| nmxutil.Assert(false) |
| return nil, fmt.Errorf("internal error: invalid master state=%+v", |
| m.state) |
| } |
| } |
| |
| c, err := initiate() |
| if err != nil { |
| return err |
| } |
| |
| if c == nil { |
| return nil |
| } |
| |
| return <-c.ch |
| } |
| |
| func (m *Master) AcquireSecondary() error { |
| initiate := func() (chan error, error) { |
| m.mtx.Lock() |
| defer m.mtx.Unlock() |
| |
| switch m.state { |
| case MASTER_STATE_IDLE: |
| // The resource is unused; just acquire it. |
| m.setState(MASTER_STATE_SECONDARY) |
| return nil, nil |
| |
| case MASTER_STATE_SECONDARY, MASTER_STATE_PRIMARY_SECONDARY_PENDING: |
| nmxutil.Assert(false) |
| return nil, fmt.Errorf("Attempt to perform more than one " + |
| "secondary master procedure") |
| |
| case MASTER_STATE_PRIMARY: |
| m.setState(MASTER_STATE_PRIMARY_SECONDARY_PENDING) |
| return m.secondaryReadyCh, nil |
| |
| default: |
| nmxutil.Assert(false) |
| return nil, fmt.Errorf( |
| "internal error: invalid master state=%+v", m.state) |
| } |
| } |
| |
| ch, err := initiate() |
| if err != nil { |
| return err |
| } |
| |
| if ch == nil { |
| return nil |
| } |
| |
| return <-ch |
| } |
| |
| func (m *Master) serviceSecondary(err error) { |
| m.secondaryReadyCh <- err |
| } |
| |
| func (m *Master) servicePrimary(err error) { |
| nmxutil.Assert(len(m.primaries) > 0) |
| |
| next := m.primaries[0] |
| m.primaries = m.primaries[1:] |
| |
| next.ch <- err |
| } |
| |
| func (m *Master) Release() { |
| m.mtx.Lock() |
| defer m.mtx.Unlock() |
| |
| switch m.state { |
| case MASTER_STATE_IDLE: |
| nmxutil.Assert(false) |
| |
| case MASTER_STATE_SECONDARY: |
| if len(m.primaries) == 0 { |
| m.setState(MASTER_STATE_IDLE) |
| } else { |
| m.setState(MASTER_STATE_PRIMARY) |
| m.servicePrimary(nil) |
| } |
| |
| case MASTER_STATE_PRIMARY: |
| if len(m.primaries) == 0 { |
| m.setState(MASTER_STATE_IDLE) |
| } else { |
| m.servicePrimary(nil) |
| } |
| |
| case MASTER_STATE_PRIMARY_SECONDARY_PENDING: |
| if len(m.primaries) == 0 { |
| m.setState(MASTER_STATE_SECONDARY) |
| m.serviceSecondary(nil) |
| } else { |
| m.servicePrimary(nil) |
| } |
| |
| default: |
| nmxutil.Assert(false) |
| } |
| } |
| |
| func (m *Master) findPrimaryIdx(token interface{}) int { |
| for i, c := range m.primaries { |
| if c.token == token { |
| return i |
| } |
| } |
| |
| return -1 |
| } |
| |
| // Removes the specified primary from the wait queue. |
| func (m *Master) StopWaitingPrimary(token interface{}, err error) { |
| m.mtx.Lock() |
| defer m.mtx.Unlock() |
| |
| idx := m.findPrimaryIdx(token) |
| if idx == -1 { |
| return |
| } |
| |
| m.primaries = append( |
| m.primaries[0:idx], m.primaries[idx+1:len(m.primaries)]...) |
| } |
| |
| // Removes the specified secondary from the wait queue. |
| func (m *Master) StopWaitingSecondary(err error) { |
| m.mtx.Lock() |
| defer m.mtx.Unlock() |
| |
| if m.state == MASTER_STATE_PRIMARY_SECONDARY_PENDING { |
| m.setState(MASTER_STATE_PRIMARY) |
| m.serviceSecondary(fmt.Errorf("secondary aborted master acquisition")) |
| } |
| } |
| |
| // Releases the resource and clears the wait queue. |
| func (m *Master) Abort(err error) { |
| m.mtx.Lock() |
| defer m.mtx.Unlock() |
| |
| // Set state such that there are no waiters. We can't clear the current |
| // owner of the master; it must release on its own. |
| switch m.state { |
| case MASTER_STATE_IDLE: |
| case MASTER_STATE_SECONDARY: |
| go m.secondary.Preempt() |
| case MASTER_STATE_PRIMARY: |
| case MASTER_STATE_PRIMARY_SECONDARY_PENDING: |
| m.serviceSecondary(err) |
| m.setState(MASTER_STATE_PRIMARY) |
| } |
| |
| for len(m.primaries) > 0 { |
| m.servicePrimary(err) |
| } |
| } |